From 5d98a4bcb704694edd3cdcfd59e26cf1c1d63269 Mon Sep 17 00:00:00 2001 From: PineaFan Date: Thu, 19 Jan 2023 16:15:47 +0000 Subject: [PATCH 01/74] Coded wanted to host the liveshare --- events2do | 30 - package-lock.json | 5094 --------------------- src/Unfinished/all.ts | 26 +- src/commands/mod/ban.ts | 12 +- src/commands/mod/softban.ts | 12 +- src/commands/settings/logs/attachment.ts | 2 +- src/commands/settings/logs/channel.ts | 2 +- src/commands/settings/logs/staff.ts | 2 +- src/commands/settings/stats.ts | 246 +- src/commands/settings/tickets.ts | 2 +- src/commands/settings/welcome.ts | 4 +- src/commands/user/track.ts | 5 +- src/config/format.ts | 7 + src/events/channelDelete.ts | 1 - src/events/channelUpdate.ts | 2 +- src/utils/commandRegistration/register.ts | 3 +- src/utils/generateEmojiEmbed.ts | 10 +- 17 files changed, 76 insertions(+), 5384 deletions(-) delete mode 100644 events2do delete mode 100644 package-lock.json diff --git a/events2do b/events2do deleted file mode 100644 index 15f4cbd..0000000 --- a/events2do +++ /dev/null @@ -1,30 +0,0 @@ --rw-r--r-- 1 pineapplefan pineapplefan 2735 Dec 29 11:03 channelCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 3661 Dec 29 11:04 channelDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 7430 Nov 15 20:45 channelUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 960 Nov 15 20:39 commandError.ts --rw-r--r-- 1 pineapplefan pineapplefan 1097 Aug 8 21:15 emojiCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1184 Aug 8 21:15 emojiDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 1183 Aug 8 21:15 emojiUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1849 Dec 29 11:04 guildBanAdd.ts --rw-r--r-- 1 pineapplefan pineapplefan 1558 Dec 29 11:04 guildBanRemove.ts --rw-r--r-- 1 pineapplefan pineapplefan 267 Dec 29 11:04 guildCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 5553 Dec 29 11:04 guildMemberUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 3863 Jan 6 17:42 guildUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1710 Jan 6 18:38 interactionCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1482 Dec 29 11:04 inviteCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1471 Dec 29 11:04 inviteDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 1387 Dec 29 11:04 memberJoin.ts --rw-r--r-- 1 pineapplefan pineapplefan 3260 Jan 2 21:41 memberLeave.ts --rw-r--r-- 1 pineapplefan pineapplefan 14919 Dec 29 11:04 messageCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 4907 Dec 29 11:04 ! messageDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 4907 Dec 29 11:04 ? messageEdit.ts: Check message publishing --rw-r--r-- 1 pineapplefan pineapplefan 1268 Dec 29 11:04 roleCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1915 Dec 29 11:04 roleDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 2562 Dec 29 11:04 roleUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1262 Dec 29 11:04 stickerCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1349 Dec 29 11:04 stickerDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 1272 Dec 29 11:04 stickerUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 1967 Dec 29 11:04 threadCreate.ts --rw-r--r-- 1 pineapplefan pineapplefan 2140 Dec 29 11:04 threadDelete.ts --rw-r--r-- 1 pineapplefan pineapplefan 2464 Dec 29 11:04 threadUpdate.ts --rw-r--r-- 1 pineapplefan pineapplefan 6352 Dec 29 11:04 webhookUpdate.ts \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0d56268..0000000 --- a/package-lock.json +++ /dev/null @@ -1,5094 +0,0 @@ -{ - "name": "nucleus", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nucleus", - "version": "0.0.1", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@discordjs/rest": "^0.2.0-canary.0", - "@hokify/agenda": "^6.2.12", - "@tsconfig/node18-strictest-esm": "^1.0.0", - "@types/node-cron": "^3.0.1", - "@ungap/structured-clone": "^1.0.1", - "agenda": "^4.3.0", - "ansi-styles": "^6.1.0", - "body-parser": "^1.20.0", - "chalk": "^5.0.0", - "deno": "^0.1.1", - "discord.js": "14.7.1", - "eslint": "^8.21.0", - "express": "^4.18.1", - "form-data": "^4.0.0", - "fuse.js": "^6.6.2", - "humanize-duration": "^3.27.1", - "immutable": "^4.1.0", - "mongodb": "^4.7.0", - "node-cron": "^3.0.0", - "node-fetch": "^3.3.0", - "node-tesseract-ocr": "^2.2.1", - "pastebin-api": "^5.1.1", - "structured-clone": "^0.2.2", - "systeminformation": "^5.17.3", - "typescript": "^5.0.0-dev.20230102", - "uuid": "^8.3.2" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.32.0", - "@typescript-eslint/parser": "^5.32.0", - "eslint-config-prettier": "^8.5.0", - "prettier": "^2.7.1", - "prettier-eslint": "^15.0.1", - "tsc-suppress": "^1.0.7" - } - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", - "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", - "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^2.0.0", - "@aws-crypto/sha256-js": "^2.0.0", - "@aws-crypto/supports-web-crypto": "^2.0.0", - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@aws-crypto/sha256-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.2.tgz", - "integrity": "sha512-iXLdKH19qPmIC73fVCrHWCSYjN/sxaAvZ3jNNyw6FclmHyjLKg0f69WlC9KTnyElxCR5MO9SKaG00VwlJwyAkQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^2.0.2", - "@aws-sdk/types": "^3.110.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", - "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", - "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", - "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "^3.110.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-sdk/abort-controller": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz", - "integrity": "sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.252.0.tgz", - "integrity": "sha512-IHdrzMUGEQcUP7vN/wbVbRCHBXhC0nyaRxnnoHbrJfh5fzPSnkwo7qNf0e8ox+GXq8xgM58dEXefA6/TMYQPFQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/client-sts": "3.252.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-node": "3.252.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.245.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.252.0.tgz", - "integrity": "sha512-VgBqJvvCU4y9zAHJwYj5nOeNGcCxKdCO4edUxWQVHcpLsVWu49maOVtWuteq9MOrHYeWfQi8bVWGt8MPvv9+bA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.245.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.252.0.tgz", - "integrity": "sha512-OOwfEXFS+UliGZorEleARsXXUp3ObZSXo9/YY+8XF7/8froAqYjKCEi0tflghgYlh7d6qe7wzD7/6gDL1a/qgA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.245.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.252.0.tgz", - "integrity": "sha512-wzfsWOlDFLdmeML8R7DUJWGl9wcRKf2uiunfB1aWzpdlgms0Z7FkHWgkDYHjCPyYHL6EBm84ajGl1UkE7AcmqQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-node": "3.252.0", - "@aws-sdk/fetch-http-handler": "3.226.0", - "@aws-sdk/hash-node": "3.226.0", - "@aws-sdk/invalid-dependency": "3.226.0", - "@aws-sdk/middleware-content-length": "3.226.0", - "@aws-sdk/middleware-endpoint": "3.226.0", - "@aws-sdk/middleware-host-header": "3.226.0", - "@aws-sdk/middleware-logger": "3.226.0", - "@aws-sdk/middleware-recursion-detection": "3.226.0", - "@aws-sdk/middleware-retry": "3.235.0", - "@aws-sdk/middleware-sdk-sts": "3.226.0", - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/middleware-user-agent": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/node-http-handler": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/smithy-client": "3.234.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.234.0", - "@aws-sdk/util-defaults-mode-node": "3.234.0", - "@aws-sdk/util-endpoints": "3.245.0", - "@aws-sdk/util-retry": "3.229.0", - "@aws-sdk/util-user-agent-browser": "3.226.0", - "@aws-sdk/util-user-agent-node": "3.226.0", - "@aws-sdk/util-utf8-browser": "3.188.0", - "@aws-sdk/util-utf8-node": "3.208.0", - "fast-xml-parser": "4.0.11", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/fast-xml-parser": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", - "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", - "license": "MIT", - "optional": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, - "node_modules/@aws-sdk/config-resolver": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz", - "integrity": "sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.252.0.tgz", - "integrity": "sha512-QW3pBYetF06FOQ85FbsFjK6xpon8feF/UOHsL0lMGi4CxUZE6zshV/ectU7tACcc4QV8uMvN7OgcK947CMEEWA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.252.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz", - "integrity": "sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz", - "integrity": "sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.252.0.tgz", - "integrity": "sha512-OfpU8xMYK7+6XQ2dUO4rN0gUhhb/ZLV7iwSL6Ji2pI9gglGhKdOSfmbn6fBfCB50kzWZRNoiQJVaBu/d0Kr0EQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.252.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.252.0.tgz", - "integrity": "sha512-Jt854JnB7izkJ/gb3S0hBFqAQPUNUP3eL8gXX2uqk9A9bQFQdS57/Ci0FXaEPwOXzJwAAPazD8dTf6HXMhnm3w==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-ini": "3.252.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.252.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz", - "integrity": "sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.252.0.tgz", - "integrity": "sha512-2JGoojMOBjG9/DenctEszjdPechq0uDTpH5nx+z1xxIAugA5+HYG/ncNfpwhmUBCrnOxpRaQViTNqXddEPHlAg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso": "3.252.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/token-providers": "3.252.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz", - "integrity": "sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.252.0.tgz", - "integrity": "sha512-aA4kwbvSlEcS9QSSlUWoVyoMYEljhkubNxpRhRnObsl4iT9xS06c38lKyhz3m0XIbCVk0lgYTcpue0dlybKS7Q==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.252.0", - "@aws-sdk/client-sso": "3.252.0", - "@aws-sdk/client-sts": "3.252.0", - "@aws-sdk/credential-provider-cognito-identity": "3.252.0", - "@aws-sdk/credential-provider-env": "3.226.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/credential-provider-ini": "3.252.0", - "@aws-sdk/credential-provider-node": "3.252.0", - "@aws-sdk/credential-provider-process": "3.226.0", - "@aws-sdk/credential-provider-sso": "3.252.0", - "@aws-sdk/credential-provider-web-identity": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz", - "integrity": "sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/querystring-builder": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-base64": "3.208.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/hash-node": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz", - "integrity": "sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz", - "integrity": "sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", - "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz", - "integrity": "sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz", - "integrity": "sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/url-parser": "3.226.0", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz", - "integrity": "sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz", - "integrity": "sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz", - "integrity": "sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry": { - "version": "3.235.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz", - "integrity": "sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/service-error-classification": "3.229.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-middleware": "3.226.0", - "@aws-sdk/util-retry": "3.229.0", - "tslib": "^2.3.1", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz", - "integrity": "sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-serde": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz", - "integrity": "sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz", - "integrity": "sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/signature-v4": "3.226.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-middleware": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz", - "integrity": "sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz", - "integrity": "sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-config-provider": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz", - "integrity": "sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz", - "integrity": "sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/abort-controller": "3.226.0", - "@aws-sdk/protocol-http": "3.226.0", - "@aws-sdk/querystring-builder": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/property-provider": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz", - "integrity": "sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/protocol-http": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz", - "integrity": "sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-builder": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz", - "integrity": "sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-parser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz", - "integrity": "sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/service-error-classification": { - "version": "3.229.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz", - "integrity": "sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz", - "integrity": "sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz", - "integrity": "sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.226.0", - "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.226.0", - "@aws-sdk/util-uri-escape": "3.201.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz", - "integrity": "sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-stack": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.252.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.252.0.tgz", - "integrity": "sha512-xi3pUP31tyKF4lJFCOgtkwSWESE9W1vE23Vybsq53wzXEYfnRql8RP+C9FFkUouAR6ixPHEcEYplB+l92CY49g==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.252.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/shared-ini-file-loader": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.226.0.tgz", - "integrity": "sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/url-parser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz", - "integrity": "sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/querystring-parser": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-base64": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", - "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", - "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", - "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", - "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-config-provider": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", - "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz", - "integrity": "sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.234.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz", - "integrity": "sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/config-resolver": "3.234.0", - "@aws-sdk/credential-provider-imds": "3.226.0", - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/property-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.245.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.245.0.tgz", - "integrity": "sha512-UNOFquB1tKx+8RT8n82Zb5tIwDyZHVPBg/m0LB0RsLETjr6krien5ASpqWezsXKIR1hftN9uaxN4bvf2dZrWHg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", - "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", - "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-middleware": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz", - "integrity": "sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-retry": { - "version": "3.229.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz", - "integrity": "sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/service-error-classification": "3.229.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", - "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz", - "integrity": "sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.226.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.226.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz", - "integrity": "sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.226.0", - "@aws-sdk/types": "3.226.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", - "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-utf8-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz", - "integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@discordjs/builders": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.4.0.tgz", - "integrity": "sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/util": "^0.1.0", - "@sapphire/shapeshift": "^3.7.1", - "discord-api-types": "^0.37.20", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.2", - "tslib": "^2.4.1" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/collection": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.3.0.tgz", - "integrity": "sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@discordjs/rest": { - "version": "0.2.0-canary.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.2.0-canary.0.tgz", - "integrity": "sha512-jOxz1aqTEzn9N0qaJcZbHz6FbA0oq+vjpXUKkQzgfMihO6gC+kLlpRnFqG25T/aPYbjaR1UM/lGhrGBB1dutqg==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^0.3.2", - "@sapphire/async-queue": "^1.1.9", - "@sapphire/snowflake": "^3.0.0", - "discord-api-types": "^0.25.2", - "form-data": "^4.0.0", - "node-fetch": "^2.6.5", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.3.2.tgz", - "integrity": "sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@discordjs/rest/node_modules/discord-api-types": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.25.2.tgz", - "integrity": "sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/@discordjs/rest/node_modules/node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@discordjs/rest/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/@discordjs/rest/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/@discordjs/util": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz", - "integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@hokify/agenda": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@hokify/agenda/-/agenda-6.3.0.tgz", - "integrity": "sha512-fWrKMDe/8QHJXLOdEsMogb6cb213Z82iNsnU7nFrSIMFifEXSkXNTyCZ99FV3KLf+Du1gS/M9/8uTC6FHyWRZQ==", - "license": "MIT", - "dependencies": { - "cron-parser": "^4", - "date.js": "~0.3.3", - "debug": "~4", - "human-interval": "~2", - "luxon": "^3", - "mongodb": "^4" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@hokify/agenda/node_modules/cron-parser": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.7.1.tgz", - "integrity": "sha512-WguFaoQ0hQ61SgsCZLHUcNbAvlK0lypKXu62ARguefYmjzaOXIVRNrAmyXzabTwUn4sQvQLkk6bjH+ipGfw8bA==", - "license": "MIT", - "dependencies": { - "luxon": "^3.2.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "license": "BSD-3-Clause" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sapphire/async-queue": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", - "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/shapeshift": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz", - "integrity": "sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/snowflake": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.4.0.tgz", - "integrity": "sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@tsconfig/node18-strictest-esm": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node18-strictest-esm/-/node18-strictest-esm-1.0.1.tgz", - "integrity": "sha512-cHzmAqw7CMbyqROWeBgVhard3F2V6zxOSJnQ4E6SJWruXD5ypuP9/QKekwBdfXQ4oUTaizIICKIwb+v3v33t0w==", - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", - "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", - "license": "MIT" - }, - "node_modules/@types/node-cron": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.7.tgz", - "integrity": "sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA==", - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", - "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/type-utils": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", - "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", - "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz", - "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.48.2", - "@typescript-eslint/utils": "5.48.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", - "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", - "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/visitor-keys": "5.48.2", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz", - "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.48.2", - "@typescript-eslint/types": "5.48.2", - "@typescript-eslint/typescript-estree": "5.48.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", - "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.48.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.0.1.tgz", - "integrity": "sha512-zKVyTt6rELvPXYwcVPTJcPFtY0AckN5A7xWuc7owBqR0FdtuDYhE9MZZUi6IY1kZUQFSXV1B3UOOIyLkVHYd2w==", - "license": "ISC" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agenda": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/agenda/-/agenda-4.4.0.tgz", - "integrity": "sha512-7fIO4indmmrtkDmj2woOBJnhHIM7jPtkdGR4VOApB46eeBrPGUnO28RFrmjHebc3PMDnKJI0PWFyu9L9VotgJg==", - "license": "MIT", - "dependencies": { - "cron-parser": "^3.0.0", - "date.js": "~0.3.3", - "debug": "~4.3.0", - "human-interval": "~2.0.0", - "moment-timezone": "~0.5.37", - "mongodb": "^4.1.0" - }, - "engines": { - "node": ">=12.9.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "license": "MIT", - "optional": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "license": "Apache-2.0", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cron-parser": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz", - "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==", - "license": "MIT", - "dependencies": { - "is-nan": "^1.3.2", - "luxon": "^1.26.0" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cron-parser/node_modules/luxon": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", - "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dafo": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dafo/-/dafo-0.1.1.tgz", - "integrity": "sha512-18XhtDt5qQ5pAfWzo1t3mRXQPanY8diFQFjCxh+/0mko5QHztCv6oLgPL9kKxB8aeYutp8lWC7BqjGzC8GWX1w==", - "dependencies": { - "noda": "^0.1.2" - } - }, - "node_modules/dafo/node_modules/noda": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/noda/-/noda-0.1.2.tgz", - "integrity": "sha512-oP5xzQsC4dujsuRlshsnb2R+0BWJmPRJx8MXQlTgbrSSqi5Szeu67Lr8Od1/lkc+8evLPRDmHl9L0xeVJYRGaw==", - "license": "ISC" - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/date.js": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", - "integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==", - "license": "MIT", - "dependencies": { - "debug": "~3.1.0" - } - }, - "node_modules/date.js/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "license": "MIT" - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "license": "MIT", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/deno": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/deno/-/deno-0.1.1.tgz", - "integrity": "sha512-vdXCOZOXXgRUFPIV2HbyBBT+u1TjKmDueCEJuk9Q+ADLS7jmVfdfGxp0vI5o/gOjbA1OwOYMVfg249+9vjYa7w==", - "license": "MIT", - "dependencies": { - "dafo": "^0.1.1", - "noda": "^0.6.0", - "qir": "^0.1.0" - }, - "bin": { - "install.js": "try-deno" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/discord-api-types": { - "version": "0.37.28", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.28.tgz", - "integrity": "sha512-K0fw7m7km9th3dCQ2AR90q/FwX3uAj+OLc+Zuo39VY9vCn0Ux/iObM4y1zJYIH3vTc+QlrksVErUvyeONjOKMQ==", - "license": "MIT" - }, - "node_modules/discord.js": { - "version": "14.7.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.7.1.tgz", - "integrity": "sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/builders": "^1.4.0", - "@discordjs/collection": "^1.3.0", - "@discordjs/rest": "^1.4.0", - "@discordjs/util": "^0.1.0", - "@sapphire/snowflake": "^3.2.2", - "@types/ws": "^8.5.3", - "discord-api-types": "^0.37.20", - "fast-deep-equal": "^3.1.3", - "lodash.snakecase": "^4.1.1", - "tslib": "^2.4.1", - "undici": "^5.13.0", - "ws": "^8.11.0" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/discord.js/node_modules/@discordjs/rest": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.5.0.tgz", - "integrity": "sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^1.3.0", - "@discordjs/util": "^0.1.0", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.2.2", - "discord-api-types": "^0.37.23", - "file-type": "^18.0.0", - "tslib": "^2.4.1", - "undici": "^5.13.0" - }, - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz", - "integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==", - "license": "MIT", - "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", - "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "license": "MIT" - }, - "node_modules/fast-xml-parser": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.13.tgz", - "integrity": "sha512-g+OboAw8ol1FgTHhKLR7ZHcItNudceiY04BBrvqa0JBWoPhi/+e5r4H5AeW+EsQCroJLJwsuOP3dD3c6cc5uOg==", - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-type": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.2.0.tgz", - "integrity": "sha512-M3RQMWY3F2ykyWZ+IHwNCjpnUmukYhtdkGGC1ZVEUb0ve5REGF7NNJ4Q9ehCUabtQKtSVFOMbFTXgJlFb0DQIg==", - "license": "MIT", - "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "license": "ISC" - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "license": "MIT" - }, - "node_modules/fuse.js": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", - "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "license": "MIT" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/human-interval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz", - "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==", - "license": "MIT", - "dependencies": { - "numbered": "^1.1.0" - } - }, - "node_modules/humanize-duration": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.28.0.tgz", - "integrity": "sha512-jMAxraOOmHuPbffLVDKkEKi/NeG8dMqP8lGRd6Tbf7JgAeG33jjgPWDbXXU7ypCI0o+oNKJFgbSB9FKVdWNI2A==", - "license": "Unlicense" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", - "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "license": "MIT" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "license": "MIT" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT" - }, - "node_modules/loglevel": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", - "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/loglevel-colored-level-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", - "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "loglevel": "^1.4.1" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/luxon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", - "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT", - "optional": true - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.40", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", - "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", - "license": "MIT", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mongodb": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.13.0.tgz", - "integrity": "sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw==", - "license": "Apache-2.0", - "dependencies": { - "bson": "^4.7.0", - "mongodb-connection-string-url": "^2.5.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "license": "MIT" - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/noda": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/noda/-/noda-0.6.0.tgz", - "integrity": "sha512-Zj3eTQ1cL83zGvorxbGmeqNnt/h+2nH3jT/XLI2oXHL9LH6IKoPvFh6feT1e/yFhRRByP3Q+waM+2dcXIdZkqg==", - "license": "ISC", - "engines": { - "node": ">=6" - } - }, - "node_modules/node-cron": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", - "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", - "license": "ISC", - "dependencies": { - "uuid": "8.3.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz", - "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-tesseract-ocr": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-tesseract-ocr/-/node-tesseract-ocr-2.2.1.tgz", - "integrity": "sha512-Q9cD79JGpPNQBxbi1fV+OAsTxYKLpx22sagsxSyKbu1u+t6UarApf5m32uVc8a5QAP1Wk7fIPN0aJFGGEE9DyQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/numbered": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz", - "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==", - "license": "MIT" - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pastebin-api": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/pastebin-api/-/pastebin-api-5.1.5.tgz", - "integrity": "sha512-MdRsE8crPA5y4M9IkLcohmU+xeLnUxMj091L5LR+Ywvp3s8bPYu7s7G1BEsvTxaJoLrgC/uPs1MgDliyqxMcNA==", - "license": "MIT", - "dependencies": { - "fast-xml-parser": "^4.0.10", - "undici": "^5.10.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/peek-readable": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", - "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", - "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-eslint": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-15.0.1.tgz", - "integrity": "sha512-mGOWVHixSvpZWARqSDXbdtTL54mMBxc5oQYQ6RAqy8jecuNJBgN3t9E5a81G66F8x8fsKNiR1HWaBV66MJDOpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "^8.4.2", - "@types/prettier": "^2.6.0", - "@typescript-eslint/parser": "^5.10.0", - "common-tags": "^1.4.0", - "dlv": "^1.1.0", - "eslint": "^8.7.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^2.5.1", - "pretty-format": "^23.0.1", - "require-relative": "^0.8.7", - "typescript": "^4.5.4", - "vue-eslint-parser": "^8.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/prettier-eslint/node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz", - "integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qir": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/qir/-/qir-0.1.0.tgz", - "integrity": "sha512-LJUKDMyPsaog5uDarzqterz+SfdyA3mfvq45kSLV0X5IOSPy/nlueKvLg8nClOSKp57c1E0bU/BFHwHFUoVhcw==", - "license": "ISC", - "dependencies": { - "noda": "^0.6.0" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "license": "MIT", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "license": "MIT", - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", - "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/structured-clone": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/structured-clone/-/structured-clone-0.2.2.tgz", - "integrity": "sha512-SucNWVxwmfAjWrzQ9Xsuv4JIDtS/Qpx+MwZD2NEx2CeMpf3hgqvWKssll34trTu6M7ywd7WZDDKO8hhq0SZiAA==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/systeminformation": { - "version": "5.17.3", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.17.3.tgz", - "integrity": "sha512-IAmnUJdeFUWqY+YneAWJ9rceTdRRIaTiwspvd1B6SG7yhqpxLrSosHgGZKiE8lcaBlBYpLQpY3BRLtus4n8PNQ==", - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32", - "freebsd", - "openbsd", - "netbsd", - "sunos", - "android" - ], - "bin": { - "systeminformation": "lib/cli.js" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "Buy me a coffee", - "url": "https://www.buymeacoffee.com/systeminfo" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/ts-mixer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz", - "integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==", - "license": "MIT" - }, - "node_modules/tsc-suppress": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tsc-suppress/-/tsc-suppress-1.0.7.tgz", - "integrity": "sha512-keT8/tFADvf1nc9CGxvMEfkfCdKp5aF2t1v9GaCRtNegljAtk1Kv0C3KLBFrZTaptgB4OXUF6QkE3eMqy6+VNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4", - "minimist": "^1.2.5" - }, - "bin": { - "tsc-suppress": "bin/tsc-suppress.js" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "typescript": ">=3.0.0" - } - }, - "node_modules/tsc-suppress/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/tsc-suppress/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/tsc-suppress/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/tsc-suppress/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.0.0-dev.20230118", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.0-dev.20230118.tgz", - "integrity": "sha512-mLhr9b2PSXo21+f210MSRD3EOdsrOg9NTWghkJNDaY0K7iWVK8E5FsflAsRzi+Rn/CsO7tH3pyl0LeGwVX25Cg==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/undici": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.15.0.tgz", - "integrity": "sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==", - "license": "MIT", - "dependencies": { - "busboy": "^1.6.0" - }, - "engines": { - "node": ">=12.18" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vue-eslint-parser": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", - "integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.2", - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.0.0", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "license": "MIT", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", - "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts index 9d9e653..d09ca7b 100644 --- a/src/Unfinished/all.ts +++ b/src/Unfinished/all.ts @@ -5,7 +5,9 @@ import Discord, { ActionRowBuilder, ButtonBuilder, SelectMenuBuilder, - ButtonStyle + ButtonStyle, + StringSelectMenuBuilder, + APIMessageComponentEmoji } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; @@ -171,8 +173,8 @@ const callback = async (interaction: CommandInteraction): Promise => { const all = true; while (true) { let count = 0; - const affected = []; - const members = interaction.guild.members.cache; + const affected: GuildMember[] = []; + const members = interaction.guild!.members.cache; if (all) { members.forEach((member) => { let applies = true; @@ -224,8 +226,8 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStatus("Success") ], components: [ - new ActionRowBuilder().addComponents([ - new SelectMenuBuilder() + new ActionRowBuilder().addComponents([ + new StringSelectMenuBuilder() .setOptions( filters.map((f, index) => ({ label: (f.inverted ? "(Not) " : "") + f.name, @@ -237,18 +239,18 @@ const callback = async (interaction: CommandInteraction): Promise => { .setCustomId("select") .setPlaceholder("Remove a filter") ]), - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new ButtonBuilder() .setLabel("Apply") .setStyle(ButtonStyle.Primary) .setCustomId("apply") - .setEmoji(client.emojis.cache.get(getEmojiByName("CONTROL.TICK", "id"))) + .setEmoji(client.emojis.cache.get(getEmojiByName("CONTROL.TICK", "id"))! as APIMessageComponentEmoji) .setDisabled(affected.length === 0), new ButtonBuilder() .setLabel("Add filter") .setStyle(ButtonStyle.Primary) .setCustomId("add") - .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.FILTER", "id"))) + .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.FILTER", "id"))! as APIMessageComponentEmoji) .setDisabled(filters.length >= 25) ]) ] @@ -260,12 +262,12 @@ const callback = async (interaction: CommandInteraction): Promise => { const check = async (interaction: CommandInteraction) => { const member = interaction.member as GuildMember; - const me = interaction.guild.me!; - if (!me.permissions.has("MANAGE_ROLES")) throw new Error("I do not have the *Manage Roles* permission"); + const me = interaction.guild!.members.me!; + if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission"; // Allow the owner to role anyone - if (member.id === interaction.guild.ownerId) return true; + if (member.id === interaction.guild!.ownerId) return true; // Check if the user has manage_roles permission - if (!member.permissions.has("MANAGE_ROLES")) throw new Error("You do not have the *Manage Roles* permission"); + if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; // Allow role return true; }; diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 70e904c..ebc44ad 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -181,19 +181,19 @@ const check = async (interaction: CommandInteraction) => { apply = apply as User } // Do not allow banning the owner - if (member.id === interaction.guild.ownerId) throw new Error("You cannot ban the owner of the server"); + if (member.id === interaction.guild.ownerId) return "You cannot ban the owner of the server"; // Check if Nucleus can ban the member - if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member"); + if (!(mePos > applyPos)) return "I do not have a role higher than that member"; // Check if Nucleus has permission to ban - if (!me.permissions.has("BanMembers")) throw new Error("I do not have the *Ban Members* permission"); + if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission"; // Do not allow banning Nucleus - if (member.id === me.id) throw new Error("I cannot ban myself"); + if (member.id === me.id) return "I cannot ban myself"; // Allow the owner to ban anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user has ban_members permission - if (!member.permissions.has("BanMembers")) throw new Error("You do not have the *Ban Members* permission"); + if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; // Check if the user is below on the role list - if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member"); + if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow ban return true; }; diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index 2787e91..2c87420 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -182,19 +182,19 @@ const check = async (interaction: CommandInteraction) => { apply = apply as User } // Do not allow banning the owner - if (member.id === interaction.guild.ownerId) throw new Error("You cannot softban the owner of the server"); + if (member.id === interaction.guild.ownerId) return "You cannot softban the owner of the server"; // Check if Nucleus can ban the member - if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member"); + if (!(mePos > applyPos)) return "I do not have a role higher than that member"; // Check if Nucleus has permission to ban - if (!me.permissions.has("BanMembers")) throw new Error("I do not have the *Ban Members* permission"); + if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission"; // Do not allow banning Nucleus - if (member.id === me.id) throw new Error("I cannot softban myself"); + if (member.id === me.id) return "I cannot softban myself"; // Allow the owner to ban anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user has ban_members permission - if (!member.permissions.has("BanMembers")) throw new Error("You do not have the *Ban Members* permission"); + if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; // Check if the user is below on the role list - if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member"); + if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow ban return true; }; diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 2709bee..6fb2461 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -60,7 +60,7 @@ const callback = async (interaction: CommandInteraction): Promise => { `Are you sure you want to set the attachment log channel to <#${channel.id}>?` ) .setColor("Warning") - .setFailedMessage("Attachment log channel not set", "Warning", "CHANNEL.TEXT.DELETE") + .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") .setInverted(true) .send(true); if (confirmation.cancelled) return; diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts index 992491a..9b594e8 100644 --- a/src/commands/settings/logs/channel.ts +++ b/src/commands/settings/logs/channel.ts @@ -56,7 +56,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Log Channel") .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`) .setColor("Warning") - .setFailedMessage("The log channel was not changed", "Danger", "CHANNEL.TEXT.DELETE") + .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") .setInverted(true) .send(true); if (confirmation.cancelled) return; diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts index 13125ef..3a0f395 100644 --- a/src/commands/settings/logs/staff.ts +++ b/src/commands/settings/logs/staff.ts @@ -61,7 +61,7 @@ const callback = async (interaction: CommandInteraction): Promise => { `Are you sure you want to set the staff notifications channel to <#${channel.id}>?` ) .setColor("Warning") - .setFailedMessage("Staff notifications channel not set", "Warning", "CHANNEL.TEXT.DELETE") + .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") .setInverted(true) .send(true); if (confirmation.cancelled) return; diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index cdd218b..1093bd2 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, AutocompleteInteraction } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, SelectMenuOptionBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; @@ -12,214 +12,34 @@ const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("stats") .setDescription("Controls channels which update when someone joins or leaves the server") - .addChannelOption((option) => option.setName("channel").setDescription("The channel to modify")) - .addStringOption((option) => - option - .setName("name") - .setDescription("The new channel name | Enter any text or use the extra variables like {memberCount}") - .setAutocomplete(true) - ); -const callback = async (interaction: CommandInteraction): Promise => { // TODO: This command feels unintuitive. Clicking a channel in the select menu deletes it - // instead, it should give a submenu to edit the channel, enable/disable or delete it - singleNotify("statsChannelDeleted", interaction.guild!.id, true); - const m = (await interaction.reply({ - embeds: LoadingEmbed, - ephemeral: true, - fetchReply: true - })) as Message; - let config = await client.database.guilds.read(interaction.guild!.id); - if (interaction.options.get("name")?.value as string) { - let channel; - if (Object.keys(config.stats).length >= 25) { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Stats Channel") - .setDescription("You can only have 25 stats channels in a server") - .setStatus("Danger") - ] - }); +const callback = async (interaction: CommandInteraction) => { + if (!interaction.guild) return; + let closed = false; + let page = 0; + do { + const config = await client.database.guilds.read(interaction.guild.id); + const stats = config.stats; // stats: Record + if (!stats) { + await interaction.editReply({embeds: [new EmojiEmbed() + .setTitle("Stats channels") + .setDescription("You don't have ant stats channels yet") + .setStatus("Success") + .setEmoji("") + ]}) } - try { - channel = interaction.options.get("channel")?.channel as Discord.Channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Stats Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild!.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Stats Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - let newName = await convertCurlyBracketString( - interaction.options.get("name")?.value as string, - "", - "", - interaction.guild!.name, - interaction.guild!.members - ); - if (interaction.options.get("channel")?.channel!.type === Discord.ChannelType.GuildText) { - newName = newName.toLowerCase().replace(/[\s]/g, "-"); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") - .setTitle("Stats Channel") - .setDescription( - `Are you sure you want to set <#${channel.id}> to a stats channel?\n\n*Preview: ${newName.replace( - /^ +| $/g, - "" - )}*` - ) - .setColor("Warning") - .setInverted(true) - .setFailedMessage(`Could not convert <#${channel.id}> to a stats chanel.`, "Danger", "CHANNEL.TEXT.DELETE") - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - const name = interaction.options.get("name")?.value as string; - const channel = interaction.options.get("channel")?.channel as Discord.TextChannel; - await client.database.guilds.write(interaction.guild!.id, { - [`stats.${channel.id}`]: { name: name, enabled: true } - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "statsChannelUpdate", - displayName: "Stats Channel Updated", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)), - name: entry( - interaction.options.get("name")?.value as string, - `\`${interaction.options.get("name")?.value as string}\`` - ) - }, - hidden: { - guild: interaction.guild!.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Stats Channel") - .setDescription("Something went wrong and the stats channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Stats Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - await statsChannelAddCallback(client, interaction.member as GuildMember); - } - let timedOut = false; - while (!timedOut) { - config = await client.database.guilds.read(interaction.guild!.id); - const stats = config.stats; - const selectMenu = new StringSelectMenuBuilder() - .setCustomId("remove") + let pageSelect = new StringSelectMenuBuilder() + .setCustomId("page") + .setPlaceholder("Select a stats channel to manage") .setMinValues(1) - .setMaxValues(Math.max(1, Object.keys(stats).length)); - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Stats Channel") - .setDescription( - "The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list." - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents( - Object.keys(stats).length - ? [ - selectMenu - .setPlaceholder("Select a stats channel to remove, stopping it updating") - .addOptions( - Object.keys(stats).map((key) => ({ - label: interaction.guild!.channels.cache.get(key)!.name, - value: key, - description: `${stats[key]!.name}` - })) - ) - ] - : [ - selectMenu - .setPlaceholder("The server has no stats channels") - .setDisabled(true) - .setOptions([ - { - label: "*Placeholder*", - value: "placeholder", - description: "No stats channels" - } - ]) - ] - ) - ] - }); - let i: StringSelectMenuInteraction; - try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } - }) as StringSelectMenuInteraction; - } catch (e) { - timedOut = true; - continue; - } - i.deferUpdate(); - if (i.customId === "remove") { - const toRemove = i.values; - await client.database.guilds.write( - interaction.guild!.id, - null, - toRemove.map((k) => `stats.${k}`) - ); + .setMaxValues(1); + for (const [id, { name, enabled }] of Object.entries(stats)) { + pageSelect.addOption() } - } - await interaction.editReply({ - embeds: [new Discord.EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })], - components: [] - }); + // [ Action... ] -> Edit, delete, reorder + // [Back][Next][Add] + } while (!closed); + closed = true; }; const check = (interaction: CommandInteraction) => { @@ -229,21 +49,7 @@ const check = (interaction: CommandInteraction) => { return true; }; -const generateStatsChannelAutocomplete = (prompt: string): string[] => { - return [prompt]; -}; - -const autocomplete = async (interaction: AutocompleteInteraction): Promise => { - if (!interaction.guild) return []; - const prompt = interaction.options.getString("tag"); - // generateStatsChannelAutocomplete(int.options.getString("name") ?? "") - const results = generateStatsChannelAutocomplete(prompt ?? ""); - return results; -}; - - export { command }; export { callback }; -export { check }; -export { autocomplete }; \ No newline at end of file +export { check }; \ No newline at end of file diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 892a420..aa25b69 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -163,7 +163,7 @@ const callback = async (interaction: CommandInteraction): Promise => { "\nAre you sure you want to apply these settings?" ) .setColor("Warning") - .setFailedMessage("Cancelled", "Warning", "GUILD.TICKET.CLOSE") // TODO: Set Actual Message + .setFailedMessage("No changes were made", "Success", "GUILD.TICKET.OPEN") .setInverted(true) .send(true); if (confirmation.cancelled) return; diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index e7143fb..42376e8 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -103,7 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Welcome Events") .setDescription(generateKeyValueList(options)) .setColor("Warning") - .setFailedMessage("Cancelled", "Warning", "GUILD.ROLES.DELETE") //TODO: Actual Message Needed + .setFailedMessage("No changes were made", "Success", "GUILD.ROLES.CREATE") .setInverted(true) .send(true); if (confirmation.cancelled) return; @@ -324,7 +324,7 @@ const autocomplete = async (interaction: AutocompleteInteraction): Promise => { const check = async (interaction: CommandInteraction) => { const tracks = (await client.database.guilds.read(interaction.guild!.id)).tracks; - if (tracks.length === 0) throw new Error("This server does not have any tracks"); + if (tracks.length === 0) return "This server does not have any tracks"; const member = interaction.member as GuildMember; // Allow the owner to promote anyone if (member.id === interaction.guild!.ownerId) return true; @@ -223,8 +223,7 @@ const check = async (interaction: CommandInteraction) => { break; } // Check if the user has manage_roles permission - if (!managed && !member.permissions.has("ManageRoles")) - throw new Error("You do not have the *Manage Roles* permission"); + if (!managed && !member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; // Allow track return true; }; diff --git a/src/config/format.ts b/src/config/format.ts index 713a233..929552f 100644 --- a/src/config/format.ts +++ b/src/config/format.ts @@ -61,6 +61,13 @@ export default async function (walkthrough = false) { out = false; json = {}; } + if (json) { + if (json.token === defaultDict["token"] || json.developmentToken === defaultDict["developmentToken"]) { + console.log("\x1b[31m⚠ No main.json found, creating one."); + console.log(" \x1b[2mYou can edit src/config/main.json directly using template written to the file.\x1b[0m\n"); + json = {}; + } + } for (const key in defaultDict) { if (!json[key]) { if (walkthrough) { diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts index 4780700..a93e6c7 100644 --- a/src/events/channelDelete.ts +++ b/src/events/channelDelete.ts @@ -15,7 +15,6 @@ export const event = "channelDelete"; export async function callback(client: NucleusClient, channel: GuildBasedChannel) { const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = client.logger; - // const audit = auditLog.entries.filter((entry: GuildAuditLogsEntry) => entry.target!.id === channel.id).first(); const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0]; if (!auditLog) return; diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts index 7c80e12..6c8687e 100644 --- a/src/events/channelUpdate.ts +++ b/src/events/channelUpdate.ts @@ -174,7 +174,7 @@ export async function callback(client: NucleusClient, oldChannel: GuildChannel, if ((oldChannel as StageChannel).rtcRegion !== (newChannel as StageChannel).rtcRegion) changes.region = entry( [(oldChannel as StageChannel).rtcRegion ?? "Automatic", (newChannel as StageChannel).rtcRegion ?? "Automatic"], - `${(oldChannel as StageChannel).rtcRegion?.toUpperCase() ?? "Automatic"} -> ${(newChannel as StageChannel).rtcRegion?.toUpperCase() ?? "Automatic"}` + `${capitalize((oldChannel as StageChannel).rtcRegion?.toUpperCase() ?? "automatic")} -> ${capitalize((newChannel as StageChannel).rtcRegion?.toUpperCase() ?? "automatic")}` ); break; } diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 281be18..4a13807 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -189,11 +189,10 @@ async function execute(check: Function | undefined, callback: Function | undefin if (typeof result === "string") { const { NucleusColors } = client.logger return data.reply({embeds: [new EmojiEmbed() - .setTitle("") .setDescription(result) .setColor(NucleusColors.red) .setEmoji(getEmojiByName("CONTROL.BLOCKCROSS")) - ]}); + ], ephemeral: true}); }; } callback(data); diff --git a/src/utils/generateEmojiEmbed.ts b/src/utils/generateEmojiEmbed.ts index c0f17ae..2978176 100644 --- a/src/utils/generateEmojiEmbed.ts +++ b/src/utils/generateEmojiEmbed.ts @@ -13,13 +13,16 @@ class EmojiEmbed extends EmbedBuilder { description = ""; _generateTitle() { + if (this._emoji && !this._title) return getEmojiByName(this._emoji) if (this._emoji) { return `${getEmojiByName(this._emoji)} ${this._title}`; } - return this._title; + if (this._title) { return this._title }; + return ""; } override setTitle(title: string) { this._title = title; - super.setTitle(this._generateTitle()); + const proposedTitle = this._generateTitle(); + if (proposedTitle) super.setTitle(proposedTitle); return this; } override setDescription(description: string) { @@ -29,7 +32,8 @@ class EmojiEmbed extends EmbedBuilder { } setEmoji(emoji: string) { this._emoji = emoji; - super.setTitle(this._generateTitle()); + const proposedTitle = this._generateTitle(); + if (proposedTitle) super.setTitle(proposedTitle); return this; } setStatus(color: "Danger" | "Warning" | "Success") { From 3a866328830a4e8aa9877eb477d53a942dcf7447 Mon Sep 17 00:00:00 2001 From: Samuel Shuert Date: Thu, 19 Jan 2023 11:18:35 -0500 Subject: [PATCH 02/74] Delete main.json --- src/config/main.json | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/config/main.json diff --git a/src/config/main.json b/src/config/main.json deleted file mode 100644 index 64abe93..0000000 --- a/src/config/main.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - developmentToken: "Your development bot token (Used for testing in one server, rather than production)", - developmentGuildID: "Your development guild ID", - enableDevelopment: true, - token: "Your bot token", - managementGuildID: "Your management guild ID (Used for running management commands on the bot)", - owners: [], - commandsFolder: "Your built commands folder (usually dist/commands)", - eventsFolder: "Your built events folder (usually dist/events)", - messageContextFolder: "Your built message context folder (usually dist/context/messages)", - userContextFolder: "Your built user context folder (usually dist/context/users)", - verifySecret: - "If using verify, enter a code here which matches the secret sent back by your website. You can use a random code if you do not have one already. (Optional)", - mongoUrl: "Your Mongo connection string, e.g. mongodb://127.0.0.1:27017", - baseUrl: "Your website where buttons such as Verify and Role menu will link to, e.g. https://example.com/", - pastebinApiKey: "An API key for pastebin (optional)", - pastebinUsername: "Your pastebin username (optional)", - pastebinPassword: "Your pastebin password (optional)", - rapidApiKey: "Your RapidAPI key (optional), used for Unscan" -} From 66e5ca6eecbd980a1f3f47eb86ca0ae7be6b6ec5 Mon Sep 17 00:00:00 2001 From: PineaFan Date: Thu, 19 Jan 2023 16:19:20 +0000 Subject: [PATCH 03/74] Removed main.json lol --- src/config/main.json | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/config/main.json diff --git a/src/config/main.json b/src/config/main.json deleted file mode 100644 index 64abe93..0000000 --- a/src/config/main.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - developmentToken: "Your development bot token (Used for testing in one server, rather than production)", - developmentGuildID: "Your development guild ID", - enableDevelopment: true, - token: "Your bot token", - managementGuildID: "Your management guild ID (Used for running management commands on the bot)", - owners: [], - commandsFolder: "Your built commands folder (usually dist/commands)", - eventsFolder: "Your built events folder (usually dist/events)", - messageContextFolder: "Your built message context folder (usually dist/context/messages)", - userContextFolder: "Your built user context folder (usually dist/context/users)", - verifySecret: - "If using verify, enter a code here which matches the secret sent back by your website. You can use a random code if you do not have one already. (Optional)", - mongoUrl: "Your Mongo connection string, e.g. mongodb://127.0.0.1:27017", - baseUrl: "Your website where buttons such as Verify and Role menu will link to, e.g. https://example.com/", - pastebinApiKey: "An API key for pastebin (optional)", - pastebinUsername: "Your pastebin username (optional)", - pastebinPassword: "Your pastebin password (optional)", - rapidApiKey: "Your RapidAPI key (optional), used for Unscan" -} From 4a6d57102740ae7070565afb764e08e34efa0786 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Thu, 19 Jan 2023 15:54:40 -0500 Subject: [PATCH 04/74] intermediate update --- src/actions/roleMenu.ts | 4 +- src/commands/mod/about.ts | 10 +- src/commands/mod/viewas.ts | 6 +- src/commands/nucleus/stats.ts | 2 +- src/commands/settings/commands.ts | 2 +- src/commands/settings/logs/attachment.ts | 2 +- src/commands/settings/logs/channel.ts | 2 +- src/commands/settings/logs/events.ts | 4 +- src/commands/settings/logs/staff.ts | 2 +- src/commands/settings/stats.ts | 270 +++++++++++++++++++++-- src/commands/settings/tickets.ts | 17 +- src/commands/settings/verify.ts | 10 +- src/commands/settings/welcome.ts | 2 +- src/config/emojis.json | 8 +- src/config/format.ts | 1 - src/reflex/guide.ts | 5 +- src/reflex/verify.ts | 4 +- src/utils/confirmationMessage.ts | 2 +- src/utils/convertCurlyBracketString.ts | 2 +- src/utils/dualCollector.ts | 23 +- src/utils/log.ts | 3 +- 21 files changed, 308 insertions(+), 73 deletions(-) diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts index 7056fe6..31a9fcf 100644 --- a/src/actions/roleMenu.ts +++ b/src/actions/roleMenu.ts @@ -75,7 +75,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let valid = false; while (!valid) { - itt += 1; + itt ++; code = ""; for (let i = 0; i < length; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); @@ -83,7 +83,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti if (code in client.roleMenu) continue; if (itt > 1000) { itt = 0; - length += 1; + length ++; continue; } valid = true; diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts index 130cdbc..1f53afb 100644 --- a/src/commands/mod/about.ts +++ b/src/commands/mod/about.ts @@ -12,6 +12,7 @@ import Discord, { ButtonStyle, StringSelectMenuInteraction, TextInputStyle, + APIMessageComponentEmoji, } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -167,8 +168,7 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte .setLabel(value.text) .setValue(key) .setDefault(filteredTypes.includes(key)) - // @ts-expect-error - .setEmoji(getEmojiByName(value.emoji, "id")) // FIXME: This gives a type error but is valid + .setEmoji(getEmojiByName(value.emoji, "id") as APIMessageComponentEmoji) ))) ]); components = components.concat([new ActionRowBuilder().addComponents([ @@ -270,8 +270,8 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte continue; } i.deferUpdate(); - if (i.customId === "filter") { - filteredTypes = (i as StringSelectMenuInteraction).values; + if (i.customId === "filter" && i.isStringSelectMenu()) { + filteredTypes = i.values; pageIndex = null; refresh = true; } @@ -412,7 +412,7 @@ const callback = async (interaction: CommandInteraction): Promise => { timedOut = true; continue; } - if (out === null) { + if (out === null || out.isButton()) { continue; } else if (out instanceof ModalSubmitInteraction) { let toAdd = out.fields.getTextInputValue("note") || null; diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts index b176dd4..4b6899c 100644 --- a/src/commands/mod/viewas.ts +++ b/src/commands/mod/viewas.ts @@ -7,7 +7,8 @@ import Discord, { ButtonStyle, NonThreadGuildBasedChannel, StringSelectMenuOptionBuilder, - StringSelectMenuBuilder + StringSelectMenuBuilder, + APIMessageComponentEmoji } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import type { GuildBasedChannel } from "discord.js"; @@ -126,8 +127,7 @@ const callback = async (interaction: CommandInteraction): Promise => { return new StringSelectMenuOptionBuilder() .setLabel(c) .setValue((set * 25 + i).toString()) - // @ts-expect-error - .setEmoji(getEmojiByName("ICONS.CHANNEL.CATEGORY", "id")) // Again, this is valid but TS doesn't think so + .setEmoji(getEmojiByName("ICONS.CHANNEL.CATEGORY", "id") as APIMessageComponentEmoji) // Again, this is valid but TS doesn't think so .setDefault((set * 25 + i) === page) })) )} diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts index d8b2807..8330fbe 100644 --- a/src/commands/nucleus/stats.ts +++ b/src/commands/nucleus/stats.ts @@ -13,7 +13,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Stats") .setDescription(`**Servers:** ${client.guilds.cache.size}\n` + `**Ping:** \`${client.ws.ping * 2}ms\``) .setStatus("Success") - .setEmoji("GUILD.GRAPHS") + .setEmoji("SETTINGS.STATS.GREEN") ], ephemeral: true }); diff --git a/src/commands/settings/commands.ts b/src/commands/settings/commands.ts index 25034b2..5cafcc5 100644 --- a/src/commands/settings/commands.ts +++ b/src/commands/settings/commands.ts @@ -191,7 +191,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } catch (e) { continue; } - if (!out) continue + if (!out || out.isButton()) continue const buttonText = out.fields.getTextInputValue("name"); const buttonLink = out.fields.getTextInputValue("url").replace(/{id}/gi, "{id}"); const current = chosen; diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 6fb2461..e9e8dce 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -158,7 +158,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } i.deferUpdate(); if ((i.component as unknown as ButtonInteraction).customId === "clear") { - clicks += 1; + clicks ++; if (clicks === 2) { clicks = 0; await client.database.guilds.write(interaction.guild!.id, null, ["logging.announcements.channel"]); diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts index 9b594e8..0852e67 100644 --- a/src/commands/settings/logs/channel.ts +++ b/src/commands/settings/logs/channel.ts @@ -151,7 +151,7 @@ const callback = async (interaction: CommandInteraction): Promise => { i = i! i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clear") { - clicks += 1; + clicks ++; if (clicks === 2) { clicks = 0; await client.database.guilds.write(interaction.guild!.id, null, ["logging.logs.channel"]); diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index fbe79fa..7332c7b 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -83,8 +83,8 @@ const callback = async (interaction: CommandInteraction): Promise => { continue; } i.deferUpdate(); - if (i.customId === "logs") { - const selected = (i as StringSelectMenuInteraction).values; + if (i.isStringSelectMenu() && i.customId === "logs") { + const selected = i.values; const newLogs = toHexInteger(selected.map((e: string) => Object.keys(logs)[parseInt(e)]!)); await client.database.guilds.write(interaction.guild!.id, { "logging.logs.toLog": newLogs diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts index 3a0f395..d6379f0 100644 --- a/src/commands/settings/logs/staff.ts +++ b/src/commands/settings/logs/staff.ts @@ -155,7 +155,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clear") { - clicks += 1; + clicks ++; if (clicks === 2) { clicks = 0; await client.database.guilds.write(interaction.guild.id, null, ["logging.staff.channel"]); diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index 1093bd2..df99510 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,45 +1,283 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, SelectMenuOptionBuilder } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import client from "../../utils/client.js"; import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; import { callback as statsChannelAddCallback } from "../../reflex/statsChannelUpdate.js"; import singleNotify from "../../utils/singleNotify.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import createPageIndicator from "../../utils/createPageIndicator.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import type { GuildConfig } from "../../utils/database.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("stats") .setDescription("Controls channels which update when someone joins or leaves the server") +type ChangesType = Record + +const applyChanges = (baseObject: GuildConfig['stats'], changes: ChangesType): GuildConfig['stats'] => { + for (const [id, { name, enabled }] of Object.entries(changes)) { + if (!baseObject[id]) baseObject[id] = { name: "", enabled: false}; + if (name) baseObject[id]!.name = name; + if (enabled) baseObject[id]!.enabled = enabled; + } + return baseObject; +} + + const callback = async (interaction: CommandInteraction) => { + try{ if (!interaction.guild) return; + const { renderChannel } = client.logger; let closed = false; let page = 0; + const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); + let changes: ChangesType = {}; do { const config = await client.database.guilds.read(interaction.guild.id); - const stats = config.stats; // stats: Record - if (!stats) { - await interaction.editReply({embeds: [new EmojiEmbed() - .setTitle("Stats channels") - .setDescription("You don't have ant stats channels yet") - .setStatus("Success") - .setEmoji("") - ]}) - } + const stats = config.stats; + let currentID = ""; + let current: { + name: string; + enabled: boolean; + } = { + name: "", + enabled: false + }; + let description = ""; let pageSelect = new StringSelectMenuBuilder() .setCustomId("page") .setPlaceholder("Select a stats channel to manage") + .setDisabled(Object.keys(stats).length === 0) .setMinValues(1) .setMaxValues(1); - for (const [id, { name, enabled }] of Object.entries(stats)) { - pageSelect.addOption() + let actionSelect = new StringSelectMenuBuilder() + .setCustomId("action") + .setPlaceholder("Perform an action") + .setMinValues(1) + .setMaxValues(1) + .setDisabled(Object.keys(stats).length === 0) + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Edit") + .setValue("edit") + .setDescription("Edit the name of this stats channel") + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new StringSelectMenuOptionBuilder() + .setLabel("Delete") + .setValue("delete") + .setDescription("Delete this stats channel") + .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) + ); + if (Object.keys(stats).length === 0) { + description = "You do not have any stats channels set up yet" + pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No stats channels").setValue("none")) + } else { + currentID = Object.keys(stats)[page]! + current = stats[currentID]!; + current = applyChanges({ [currentID]: current }, changes)[currentID]!; + // Propogate pageSelect with list of stats channels + for (const [id, { name, enabled }] of Object.entries(stats)) { + pageSelect.addOptions( + new StringSelectMenuOptionBuilder() + .setLabel(name) + .setValue(id) + .setDescription(`Enabled: ${enabled}`) + ); + } + actionSelect.addOptions(new StringSelectMenuOptionBuilder() + .setLabel(current.enabled ? "Disable" : "Enable") + .setValue("toggleEnabled") + .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`) + .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji) + ); + description = `**Currently Editing:** ${renderChannel(currentID)}\n\n` + + `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` + + `**Name:** \`${current.name}\`\n` + + `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + } + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + .setDisabled(page === 0), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Primary) + .setDisabled(page === Object.keys(stats).length - 1), + new ButtonBuilder() + .setCustomId("add") + .setLabel("Create new") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(stats).length >= 24), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(Object.keys(changes).length === 0), + ); + + let embed = new EmojiEmbed() + .setTitle("Stats Channels") + .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page)) + .setEmoji("SETTINGS.STATS.GREEN") + .setStatus("Success") + + interaction.editReply({ + embeds: [embed], + components: [ + new ActionRowBuilder().addComponents(pageSelect), + new ActionRowBuilder().addComponents(actionSelect), + row + ] + }); + let i: MessageComponentInteraction; + try { + i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 }); + } catch (e) { + closed = true; + continue; + } + if (i.isStringSelectMenu()) { + switch(i.customId) { + case "page": + page = Object.keys(stats).indexOf(i.values[0]!); + i.deferUpdate(); + break; + case "action": + if(!changes[currentID]) changes[currentID] = {}; + switch(i.values[0]!) { + case "edit": + await i.showModal( + new Discord.ModalBuilder() + .setCustomId("modal") + .setTitle(`Stats channel name`) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex1") + .setLabel("Server Info (1/3)") + .setPlaceholder( + `{serverName} - This server's name\n\n` + + `These placeholders will be replaced with the server's name, etc..` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex2") + .setLabel("Member Counts (2/3) - {MemberCount:...}") + .setPlaceholder( + `{:all} - Total member count\n` + + `{:humans} - Total non-bot users\n` + + `{:bots} - Number of bots\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex3") + .setLabel("Latest Member (3/3) - {member:...}") + .setPlaceholder( + `{:name} - The members name\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("text") + .setLabel("Channel name input") + .setMaxLength(1000) + .setRequired(true) + .setStyle(Discord.TextInputStyle.Short) + .setValue(current.name) + ) + ) + ); + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Stats Channel") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + .setEmoji("SETTINGS.STATS.GREEN") + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Back") + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("back") + ) + ] + }); + let out: Discord.ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; + } catch (e) { + continue; + } + if (!out) continue + if (!out.fields) continue + if (out.isButton()) continue; + const newString = out.fields.getTextInputValue("text"); + if (!newString) continue; + changes[currentID]!.name = newString; + break; + case "delete": + changes[currentID] = {}; + i.deferUpdate(); + break; + case "toggleEnabled": + changes[currentID]!.enabled = !stats[currentID]!.enabled; + i.deferUpdate(); + break; + } + break; + } + } else if (i.isButton()) { + i.deferUpdate(); + switch(i.customId) { + case "back": + page--; + break; + case "next": + page++; + break; + case "add": + break; + case "save": + let changed = applyChanges(config.stats, changes); + singleNotify("statsChannelDeleted", interaction.guild.id, true) + config.stats = changed; + changes = {} + await client.database.guilds.write(interaction.guildId!, config); + } } - // [ Action... ] -> Edit, delete, reorder - // [Back][Next][Add] + console.log(changes, config.stats); } while (!closed); - closed = true; + } catch(e) { + console.log(e) + } }; const check = (interaction: CommandInteraction) => { diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index aa25b69..8f9f688 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -11,11 +11,9 @@ import Discord, { MessageComponentInteraction, StringSelectMenuBuilder, Role, - StringSelectMenuInteraction, ButtonStyle, TextInputBuilder, ButtonComponent, - StringSelectMenuComponent, ModalSubmitInteraction, APIMessageComponentEmoji } from "discord.js"; @@ -390,14 +388,14 @@ const callback = async (interaction: CommandInteraction): Promise => { innerTimedOut = true; continue; } - if ((i.component as StringSelectMenuComponent).customId === "template") { + if (i.isStringSelectMenu() && i.customId === "template") { i.deferUpdate(); await interaction.channel!.send({ embeds: [ new EmojiEmbed() - .setTitle(ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label) + .setTitle(ticketMessages[parseInt(i.values[0]!)]!.label) .setDescription( - ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description + ticketMessages[parseInt(i.values[0]!)]!.description ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") @@ -643,16 +641,16 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t timedOut = true; continue; } - if ((i.component as StringSelectMenuComponent).customId === "types") { + if (i.isStringSelectMenu() && i.customId === "types") { i.deferUpdate(); - const types = toHexInteger((i as StringSelectMenuInteraction).values, ticketTypes); + const types = toHexInteger(i.values, ticketTypes); await client.database.guilds.write(interaction.guild!.id, { "tickets.types": types }); data.types = types; - } else if ((i.component as StringSelectMenuComponent).customId === "removeTypes") { + } else if (i.isStringSelectMenu() && i.customId === "removeTypes") { i.deferUpdate(); - const types = (i as StringSelectMenuInteraction).values; + const types = i.values; let customTypes = data.customTypes; if (customTypes) { customTypes = customTypes.filter((t) => !types.includes(t)); @@ -708,6 +706,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t } catch (e) { continue; } + if (!out || out.isButton()) continue; out = out as ModalSubmitInteraction; let toAdd = out.fields.getTextInputValue("type"); if (!toAdd) { diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 0f9f4a0..d3971a8 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -10,10 +10,8 @@ import Discord, { Role, ButtonStyle, StringSelectMenuBuilder, - StringSelectMenuComponent, TextInputBuilder, EmbedBuilder, - StringSelectMenuInteraction, ButtonComponent } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -170,7 +168,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clear") { - clicks += 1; + clicks ++; if (clicks === 2) { clicks = 0; await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]); @@ -257,14 +255,14 @@ const callback = async (interaction: CommandInteraction): Promise => { innerTimedOut = true; continue; } - if ((i.component as StringSelectMenuComponent).customId === "template") { + if (i.isStringSelectMenu() && i.customId === "template") { i.deferUpdate(); await interaction.channel!.send({ embeds: [ new EmojiEmbed() - .setTitle(verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label) + .setTitle(verifyMessages[parseInt(i.values[0]!)]!.label) .setDescription( - verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description + verifyMessages[parseInt(i.values[0]!)]!.description ) .setStatus("Success") .setEmoji("CONTROL.BLOCKTICK") diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index 42376e8..abedec5 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -309,7 +309,7 @@ const check = (interaction: CommandInteraction) => { }; const autocomplete = async (interaction: AutocompleteInteraction): Promise => { - const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"] + const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"] if (!interaction.guild) return []; const prompt = interaction.options.getString("message"); const autocompletions = []; diff --git a/src/config/emojis.json b/src/config/emojis.json index 26d4c98..8c45353 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -22,6 +22,7 @@ "FILTER": "990242059451514902", "ATTACHMENT": "997570687193587812", "LOGGING": "999613304446144562", + "SAVE": "1065722246322200586", "NOTIFY": { "ON": "1000726394579464232", "OFF": "1000726363495477368" @@ -210,6 +211,12 @@ "STOP": "853519660116738078" } }, + "SETTINGS": { + "STATS": { + "GREEN": "752214059159650396", + "RED": "1065677252630675556" + } + }, "GUILD": { "RED": "959779988264079361", "YELLOW": "729763053352124529", @@ -219,7 +226,6 @@ "EDIT": "729066518549233795", "DELETE": "953035210121953320" }, - "GRAPHS": "752214059159650396", "SETTINGS": "752570111063228507", "ICONCHANGE": "729763053612302356", "TICKET": { diff --git a/src/config/format.ts b/src/config/format.ts index 929552f..b00b3e4 100644 --- a/src/config/format.ts +++ b/src/config/format.ts @@ -1,5 +1,4 @@ import fs from "fs"; -// @ts-expect-error import * as readLine from "node:readline/promises"; const defaultDict: Record = { diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts index 6829ef2..3971ad9 100644 --- a/src/reflex/guide.ts +++ b/src/reflex/guide.ts @@ -3,7 +3,6 @@ import Discord, { ActionRowBuilder, ButtonBuilder, MessageComponentInteraction, - StringSelectMenuInteraction, Guild, CommandInteraction, GuildTextBasedChannel, @@ -285,8 +284,8 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { selectPaneOpen = false; } else if (i.component.customId === "select") { selectPaneOpen = !selectPaneOpen; - } else if (i.component.customId === "page") { - page = parseInt((i as StringSelectMenuInteraction).values[0]!); + } else if (i.isStringSelectMenu() && i.component.customId === "page") { + page = parseInt(i.values[0]!); selectPaneOpen = false; } else { cancelled = true; diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts index 573da5e..f7d0396 100644 --- a/src/reflex/verify.ts +++ b/src/reflex/verify.ts @@ -182,14 +182,14 @@ export default async function (interaction: CommandInteraction | ButtonInteracti let itt = 0; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; do { - itt += 1; + itt ++; code = ""; for (let i = 0; i < length; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); } if (itt > 1000) { itt = 0; - length += 1; + length ++; } } while (code in verify); const role: Role | null = await interaction.guild!.roles.fetch(config.verify.role); diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 4d90676..613b48c 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -257,7 +257,7 @@ class confirmationMessage { cancelled = true; continue; } - if (out === null) { + if (out === null || out.isButton()) { cancelled = true; continue; } diff --git a/src/utils/convertCurlyBracketString.ts b/src/utils/convertCurlyBracketString.ts index 5d2c23d..058ba16 100644 --- a/src/utils/convertCurlyBracketString.ts +++ b/src/utils/convertCurlyBracketString.ts @@ -13,7 +13,7 @@ async function convertCurlyBracketString( .replace("{member:mention}", memberID ? `<@${memberID}>` : "{member:mention}") .replace("{member:name}", memberName ? `${memberName}` : "{member:name}") .replace("{serverName}", serverName ? `${serverName}` : "{serverName}") - .replace("{memberCount}", memberCount ? `${memberCount}` : "{memberCount}") + .replace("{memberCount:all}", memberCount ? `${memberCount}` : "{memberCount}") .replace("{memberCount:bots}", bots ? `${bots}` : "{memberCount:bots}") .replace("{memberCount:humans}", memberCount && bots ? `${memberCount - bots}` : "{memberCount:humans}"); diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index 714a2d9..3c2b5d9 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -1,4 +1,4 @@ -import Discord, { Client, Interaction, Message, MessageComponentInteraction } from "discord.js"; +import Discord, { ButtonInteraction, Client, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; import client from "./client.js"; export default async function ( @@ -49,8 +49,8 @@ export async function modalInteractionCollector( m: Message, modalFilter: (i: Interaction) => boolean | Promise, interactionFilter: (i: MessageComponentInteraction) => boolean | Promise -): Promise { - let out: Interaction; +): Promise { + let out: ButtonInteraction | ModalSubmitInteraction; try { out = await new Promise((resolve, _reject) => { const int = m @@ -58,22 +58,17 @@ export async function modalInteractionCollector( filter: (i: MessageComponentInteraction) => interactionFilter(i), time: 300000 }) - .on("collect", (i: Interaction) => { + .on("collect", (i: ButtonInteraction) => { + mod.stop(); resolve(i); }); - const mod = new Discord.InteractionCollector(client as Client, { + const mod = new InteractionCollector(client as Client, { filter: (i: Interaction) => modalFilter(i), time: 300000 - }).on("collect", async (i: Interaction) => { - int.stop(); - (i as Discord.ModalSubmitInteraction).deferUpdate(); - resolve(i as Discord.ModalSubmitInteraction); - }); - int.on("end", () => { - mod.stop(); - }); - mod.on("end", () => { + }).on("collect", async (i: ModalSubmitInteraction) => { int.stop(); + await i.deferUpdate(); + resolve(i); }); }); } catch (e) { diff --git a/src/utils/log.ts b/src/utils/log.ts index 54f656a..184f29a 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -29,7 +29,8 @@ export const Logger = { if (typeof value === "number") value = value.toString(); return { value: value, displayValue: displayValue }; }, - renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) { + renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | string) { + if (typeof channel === "string") channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel; return `${channel.name} [<#${channel.id}>]`; }, renderRole(role: Discord.Role) { From 456a20ffab6f7855636baf6b0ace2de92b2de5ea Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Thu, 19 Jan 2023 15:55:34 -0500 Subject: [PATCH 05/74] intermediate update --- src/commands/settings/stats.ts | 1 - src/utils/dualCollector.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index df99510..bd63cdc 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -4,7 +4,6 @@ import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import client from "../../utils/client.js"; import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; -import { callback as statsChannelAddCallback } from "../../reflex/statsChannelUpdate.js"; import singleNotify from "../../utils/singleNotify.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import createPageIndicator from "../../utils/createPageIndicator.js"; diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index 3c2b5d9..1a57a85 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -1,4 +1,4 @@ -import Discord, { ButtonInteraction, Client, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; +import { ButtonInteraction, Client, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; import client from "./client.js"; export default async function ( From c2acbcc955b5701328b6e678e306e2b80663c75e Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 20 Jan 2023 17:23:51 -0500 Subject: [PATCH 06/74] Nucleus stats --- src/commands/settings/oldStats.ts | 291 ++++++++++++++++++++++++++ src/commands/settings/stats.ts | 331 ++++++++++++++---------------- src/utils/createPageIndicator.ts | 12 +- 3 files changed, 447 insertions(+), 187 deletions(-) create mode 100644 src/commands/settings/oldStats.ts diff --git a/src/commands/settings/oldStats.ts b/src/commands/settings/oldStats.ts new file mode 100644 index 0000000..475b5d3 --- /dev/null +++ b/src/commands/settings/oldStats.ts @@ -0,0 +1,291 @@ +import { LoadingEmbed } from "../../utils/defaults.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import client from "../../utils/client.js"; +import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; +import singleNotify from "../../utils/singleNotify.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import createPageIndicator from "../../utils/createPageIndicator.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import type { GuildConfig } from "../../utils/database.js"; + +const command = (builder: SlashCommandSubcommandBuilder) => + builder + .setName("stats") + .setDescription("Controls channels which update when someone joins or leaves the server") + +type ChangesType = Record + +const applyChanges = (baseObject: GuildConfig['stats'], changes: ChangesType): GuildConfig['stats'] => { + for (const [id, { name, enabled }] of Object.entries(changes)) { + if (!baseObject[id]) baseObject[id] = { name: "", enabled: false}; + if (name) baseObject[id]!.name = name; + if (enabled) baseObject[id]!.enabled = enabled; + } + return baseObject; +} + + +const callback = async (interaction: CommandInteraction) => { + try{ + if (!interaction.guild) return; + const { renderChannel } = client.logger; + let closed = false; + let page = 0; + const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); + let changes: ChangesType = {}; + do { + const config = await client.database.guilds.read(interaction.guild.id); + const stats = config.stats; + let currentID = ""; + let current: { + name: string; + enabled: boolean; + } = { + name: "", + enabled: false + }; + let description = ""; + let pageSelect = new StringSelectMenuBuilder() + .setCustomId("page") + .setPlaceholder("Select a stats channel to manage") + .setDisabled(Object.keys(stats).length === 0) + .setMinValues(1) + .setMaxValues(1); + let actionSelect = new StringSelectMenuBuilder() + .setCustomId("action") + .setPlaceholder("Perform an action") + .setMinValues(1) + .setMaxValues(1) + .setDisabled(Object.keys(stats).length === 0) + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Edit") + .setValue("edit") + .setDescription("Edit the name of this stats channel") + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new StringSelectMenuOptionBuilder() + .setLabel("Delete") + .setValue("delete") + .setDescription("Delete this stats channel") + .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) + ); + if (Object.keys(stats).length === 0) { + description = "You do not have any stats channels set up yet" + pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No stats channels").setValue("none")) + } else { + currentID = Object.keys(stats)[page]! + current = stats[currentID]!; + current = applyChanges({ [currentID]: current }, changes)[currentID]!; + for (const [id, { name, enabled }] of Object.entries(stats)) { + pageSelect.addOptions( + new StringSelectMenuOptionBuilder() + .setLabel(name) + .setValue(id) + .setDescription(`Enabled: ${enabled}`) + ); + } + actionSelect.addOptions(new StringSelectMenuOptionBuilder() + .setLabel(current.enabled ? "Disable" : "Enable") + .setValue("toggleEnabled") + .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`) + .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji) + ); + description = `**Currently Editing:** ${renderChannel(currentID)}\n\n` + + `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` + + `**Name:** \`${current.name}\`\n` + + `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + } + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + .setDisabled(page === 0), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Primary) + .setDisabled(page === Object.keys(stats).length - 1), + new ButtonBuilder() + .setCustomId("add") + .setLabel("Create new") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(stats).length >= 24), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(Object.keys(changes).length === 0), + ); + + let embed = new EmojiEmbed() + .setTitle("Stats Channels") + .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page)) + .setEmoji("SETTINGS.STATS.GREEN") + .setStatus("Success") + + interaction.editReply({ + embeds: [embed], + components: [ + new ActionRowBuilder().addComponents(pageSelect), + new ActionRowBuilder().addComponents(actionSelect), + row + ] + }); + let i: MessageComponentInteraction; + try { + i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 }); + } catch (e) { + closed = true; + continue; + } + if (i.isStringSelectMenu()) { + switch(i.customId) { + case "page": + page = Object.keys(stats).indexOf(i.values[0]!); + i.deferUpdate(); + break; + case "action": + if(!changes[currentID]) changes[currentID] = {}; + switch(i.values[0]!) { + case "edit": + await i.showModal( + new Discord.ModalBuilder() + .setCustomId("modal") + .setTitle(`Stats channel name`) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex1") + .setLabel("Server Info (1/3)") + .setPlaceholder( + `{serverName} - This server's name\n\n` + + `These placeholders will be replaced with the server's name, etc..` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex2") + .setLabel("Member Counts (2/3) - {MemberCount:...}") + .setPlaceholder( + `{:all} - Total member count\n` + + `{:humans} - Total non-bot users\n` + + `{:bots} - Number of bots\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex3") + .setLabel("Latest Member (3/3) - {member:...}") + .setPlaceholder( + `{:name} - The members name\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("text") + .setLabel("Channel name input") + .setMaxLength(1000) + .setRequired(true) + .setStyle(Discord.TextInputStyle.Short) + .setValue(current.name) + ) + ) + ); + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Stats Channel") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + .setEmoji("SETTINGS.STATS.GREEN") + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Back") + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("back") + ) + ] + }); + let out: Discord.ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; + } catch (e) { + continue; + } + if (!out) continue + if (!out.fields) continue + if (out.isButton()) continue; + const newString = out.fields.getTextInputValue("text"); + if (!newString) continue; + changes[currentID]!.name = newString; + break; + case "delete": + changes[currentID] = {}; + i.deferUpdate(); + break; + case "toggleEnabled": + changes[currentID]!.enabled = !stats[currentID]!.enabled; + i.deferUpdate(); + break; + } + break; + } + } else if (i.isButton()) { + i.deferUpdate(); + switch(i.customId) { + case "back": + page--; + break; + case "next": + page++; + break; + case "add": + break; + case "save": + let changed = applyChanges(config.stats, changes); + singleNotify("statsChannelDeleted", interaction.guild.id, true) + config.stats = changed; + changes = {} + await client.database.guilds.write(interaction.guildId!, config); + } + } + console.log(changes, config.stats); + } while (!closed); + } catch(e) { + console.log(e) + } +}; + +const check = (interaction: CommandInteraction) => { + const member = interaction.member as Discord.GuildMember; + if (!member.permissions.has("ManageChannels")) + return "You must have the *Manage Channels* permission to use this command"; + return true; +}; + + +export { command }; +export { callback }; +export { check }; \ No newline at end of file diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index bd63cdc..118afc2 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import client from "../../utils/client.js"; @@ -8,205 +8,189 @@ import singleNotify from "../../utils/singleNotify.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import createPageIndicator from "../../utils/createPageIndicator.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; -import type { GuildConfig } from "../../utils/database.js"; + const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("stats") .setDescription("Controls channels which update when someone joins or leaves the server") -type ChangesType = Record -const applyChanges = (baseObject: GuildConfig['stats'], changes: ChangesType): GuildConfig['stats'] => { - for (const [id, { name, enabled }] of Object.entries(changes)) { - if (!baseObject[id]) baseObject[id] = { name: "", enabled: false}; - if (name) baseObject[id]!.name = name; - if (enabled) baseObject[id]!.enabled = enabled; - } - return baseObject; +const showModal = async (interaction: StringSelectMenuInteraction, current: { enabled: boolean; name: string; }) => { + await interaction.showModal( + new Discord.ModalBuilder() + .setCustomId("modal") + .setTitle(`Stats channel name`) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex1") + .setLabel("Server Info (1/3)") + .setPlaceholder( + `{serverName} - This server's name\n\n` + + `These placeholders will be replaced with the server's name, etc..` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex2") + .setLabel("Member Counts (2/3) - {MemberCount:...}") + .setPlaceholder( + `{:all} - Total member count\n` + + `{:humans} - Total non-bot users\n` + + `{:bots} - Number of bots\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex3") + .setLabel("Latest Member (3/3) - {member:...}") + .setPlaceholder( + `{:name} - The members name\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(Discord.TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("text") + .setLabel("Channel name input") + .setMaxLength(1000) + .setRequired(true) + .setStyle(Discord.TextInputStyle.Short) + .setValue(current.name) + ) + ) + ); } - const callback = async (interaction: CommandInteraction) => { - try{ if (!interaction.guild) return; const { renderChannel } = client.logger; - let closed = false; - let page = 0; const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); - let changes: ChangesType = {}; + let page = 0; + let closed = false; + const config = await client.database.guilds.read(interaction.guild.id); + const currentObject = config.stats; + let modified = false; do { - const config = await client.database.guilds.read(interaction.guild.id); - const stats = config.stats; - let currentID = ""; - let current: { - name: string; - enabled: boolean; - } = { - name: "", - enabled: false - }; - let description = ""; - let pageSelect = new StringSelectMenuBuilder() + let embed = new EmojiEmbed() + .setTitle("Stats Settings") + .setEmoji("SETTINGS.STATS.GREEN") + .setStatus("Success"); + const noStatsChannels = Object.keys(currentObject).length === 0; + let current: { enabled: boolean; name: string; }; + + const pageSelect = new StringSelectMenuBuilder() .setCustomId("page") - .setPlaceholder("Select a stats channel to manage") - .setDisabled(Object.keys(stats).length === 0) - .setMinValues(1) - .setMaxValues(1); - let actionSelect = new StringSelectMenuBuilder() + .setPlaceholder("Select a stats channel to manage"); + const actionSelect = new StringSelectMenuBuilder() .setCustomId("action") .setPlaceholder("Perform an action") - .setMinValues(1) - .setMaxValues(1) - .setDisabled(Object.keys(stats).length === 0) .addOptions( new StringSelectMenuOptionBuilder() .setLabel("Edit") + .setDescription("Edit the stats channel") .setValue("edit") - .setDescription("Edit the name of this stats channel") .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), new StringSelectMenuOptionBuilder() .setLabel("Delete") + .setDescription("Delete the stats channel") .setValue("delete") - .setDescription("Delete this stats channel") .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) + ); + const buttonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + .setDisabled(page === 0), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Primary) + .setDisabled(page === Object.keys(currentObject).length - 1), + new ButtonBuilder() + .setCustomId("add") + .setLabel("Create new") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(currentObject).length >= 24), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(modified), + ); + if (noStatsChannels) { + embed.setDescription("No stats channels have been set up yet. Use the button below to add one.\n\n" + + createPageIndicator(1, 1, undefined, true) + ); + pageSelect.setDisabled(true); + actionSelect.setDisabled(true); + pageSelect.addOptions(new StringSelectMenuOptionBuilder() + .setLabel("No stats channels") + .setValue("none") ); - if (Object.keys(stats).length === 0) { - description = "You do not have any stats channels set up yet" - pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No stats channels").setValue("none")) } else { - currentID = Object.keys(stats)[page]! - current = stats[currentID]!; - current = applyChanges({ [currentID]: current }, changes)[currentID]!; - // Propogate pageSelect with list of stats channels - for (const [id, { name, enabled }] of Object.entries(stats)) { - pageSelect.addOptions( - new StringSelectMenuOptionBuilder() - .setLabel(name) - .setValue(id) - .setDescription(`Enabled: ${enabled}`) - ); - } + page = Math.min(page, Object.keys(currentObject).length - 1); + current = currentObject[Object.keys(config.stats)[page]!]! actionSelect.addOptions(new StringSelectMenuOptionBuilder() .setLabel(current.enabled ? "Disable" : "Enable") .setValue("toggleEnabled") .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`) .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji) ); - description = `**Currently Editing:** ${renderChannel(currentID)}\n\n` + + + embed.setDescription(`**Currently Editing:** ${renderChannel(Object.keys(currentObject)[page]!)}\n\n` + `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` + `**Name:** \`${current.name}\`\n` + - `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` - } - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId("back") - .setStyle(ButtonStyle.Primary) - .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) - .setDisabled(page === 0), - new ButtonBuilder() - .setCustomId("next") - .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Primary) - .setDisabled(page === Object.keys(stats).length - 1), - new ButtonBuilder() - .setCustomId("add") - .setLabel("Create new") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Secondary) - .setDisabled(Object.keys(stats).length >= 24), - new ButtonBuilder() - .setCustomId("save") - .setLabel("Save") - .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Success) - .setDisabled(Object.keys(changes).length === 0), + `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + '\n\n' + + createPageIndicator(Object.keys(config.stats).length, page) ); + for (const [id, { name, enabled }] of Object.entries(currentObject)) { + pageSelect.addOptions(new StringSelectMenuOptionBuilder() + .setLabel(`${name} (${renderChannel(id)})`) + .setEmoji(getEmojiByName(enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji) + .setDescription(`${enabled ? "Enabled" : "Disabled"}`) + .setValue(id) + ); + } + } - let embed = new EmojiEmbed() - .setTitle("Stats Channels") - .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page)) - .setEmoji("SETTINGS.STATS.GREEN") - .setStatus("Success") + interaction.editReply({embeds: [embed], components: [ + new ActionRowBuilder().addComponents(pageSelect), + new ActionRowBuilder().addComponents(actionSelect), + buttonRow + ]}); - interaction.editReply({ - embeds: [embed], - components: [ - new ActionRowBuilder().addComponents(pageSelect), - new ActionRowBuilder().addComponents(actionSelect), - row - ] - }); - let i: MessageComponentInteraction; + let i: StringSelectMenuInteraction | ButtonInteraction; try { - i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 }); + i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 60000 }) as StringSelectMenuInteraction | ButtonInteraction; } catch (e) { closed = true; continue; } - if (i.isStringSelectMenu()) { + + if(i.isStringSelectMenu()) { switch(i.customId) { case "page": - page = Object.keys(stats).indexOf(i.values[0]!); - i.deferUpdate(); + page = Object.keys(currentObject).indexOf(i.values[0]!); break; case "action": - if(!changes[currentID]) changes[currentID] = {}; + modified = true; switch(i.values[0]!) { - case "edit": - await i.showModal( - new Discord.ModalBuilder() - .setCustomId("modal") - .setTitle(`Stats channel name`) - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("ex1") - .setLabel("Server Info (1/3)") - .setPlaceholder( - `{serverName} - This server's name\n\n` + - `These placeholders will be replaced with the server's name, etc..` - ) - .setMaxLength(1) - .setRequired(false) - .setStyle(Discord.TextInputStyle.Paragraph) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("ex2") - .setLabel("Member Counts (2/3) - {MemberCount:...}") - .setPlaceholder( - `{:all} - Total member count\n` + - `{:humans} - Total non-bot users\n` + - `{:bots} - Number of bots\n` - ) - .setMaxLength(1) - .setRequired(false) - .setStyle(Discord.TextInputStyle.Paragraph) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("ex3") - .setLabel("Latest Member (3/3) - {member:...}") - .setPlaceholder( - `{:name} - The members name\n` - ) - .setMaxLength(1) - .setRequired(false) - .setStyle(Discord.TextInputStyle.Paragraph) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("text") - .setLabel("Channel name input") - .setMaxLength(1000) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Short) - .setValue(current.name) - ) - ) - ); + case "edit": { + showModal(i, current!) await interaction.editReply({ embeds: [ new EmojiEmbed() @@ -225,35 +209,24 @@ const callback = async (interaction: CommandInteraction) => { ) ] }); - let out: Discord.ModalSubmitInteraction | null; - try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; - } catch (e) { - continue; - } - if (!out) continue - if (!out.fields) continue - if (out.isButton()) continue; - const newString = out.fields.getTextInputValue("text"); - if (!newString) continue; - changes[currentID]!.name = newString; break; - case "delete": - changes[currentID] = {}; + } + case "toggleEnabled": { i.deferUpdate(); + currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled; + modified = true; break; - case "toggleEnabled": - changes[currentID]!.enabled = !stats[currentID]!.enabled; + } + case "delete": { i.deferUpdate(); + delete currentObject[Object.keys(currentObject)[page]!]; + modified = true; break; + } } break; } - } else if (i.isButton()) { + } else { i.deferUpdate(); switch(i.customId) { case "back": @@ -265,18 +238,14 @@ const callback = async (interaction: CommandInteraction) => { case "add": break; case "save": - let changed = applyChanges(config.stats, changes); - singleNotify("statsChannelDeleted", interaction.guild.id, true) - config.stats = changed; - changes = {} - await client.database.guilds.write(interaction.guildId!, config); + client.database.guilds.write(interaction.guild.id, {stats: currentObject}); + singleNotify("statsChannelDeleted", interaction.guild.id, true); + modified = false; + break; } } - console.log(changes, config.stats); + } while (!closed); - } catch(e) { - console.log(e) - } }; const check = (interaction: CommandInteraction) => { diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts index ee123d6..4ddbae2 100644 --- a/src/utils/createPageIndicator.ts +++ b/src/utils/createPageIndicator.ts @@ -1,17 +1,17 @@ import getEmojiByName from "./getEmojiByName.js"; -function pageIndicator(amount: number, selected: number, showDetails?: boolean | true) { +function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) { let out = ""; - + disabled = disabled ? "GREY." : "" if (amount === 1) { - out += getEmojiByName("TRACKS.SINGLE." + (selected === 0 ? "ACTIVE" : "INACTIVE")); + out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (selected === 0 ? "ACTIVE" : "INACTIVE")); } else { for (let i = 0; i < amount; i++) { out += getEmojiByName( "TRACKS.HORIZONTAL." + - (i === 0 ? "LEFT" : i === amount - 1 ? "RIGHT" : "MIDDLE") + - "." + - (i === selected ? "ACTIVE" : "INACTIVE") + (i === 0 ? "LEFT" : i === amount - 1 ? "RIGHT" : "MIDDLE") + + "." + (disabled) + + (i === selected ? "ACTIVE" : "INACTIVE") ); } } From 9bc8475c083f61c7e886a7090cb020588316b3e3 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 21 Jan 2023 05:19:57 -0500 Subject: [PATCH 07/74] Nucleus stats --- src/commands/settings/stats.ts | 159 ++++++++++++++++++++++++++++++++- src/utils/dualCollector.ts | 6 +- 2 files changed, 159 insertions(+), 6 deletions(-) diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index 118afc2..7dd9cf4 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import client from "../../utils/client.js"; @@ -16,7 +16,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setDescription("Controls channels which update when someone joins or leaves the server") -const showModal = async (interaction: StringSelectMenuInteraction, current: { enabled: boolean; name: string; }) => { +const showModal = async (interaction: MessageComponentInteraction, current: { enabled: boolean; name: string; }) => { await interaction.showModal( new Discord.ModalBuilder() .setCustomId("modal") @@ -71,6 +71,144 @@ const showModal = async (interaction: StringSelectMenuInteraction, current: { en ); } +type ObjectSchema = Record + +/* +let out: Discord.ModalSubmitInteraction | null = null; +try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; +} catch (e) { + continue; +} +if (!out) continue +out = out!; +if (!out.fields) continue; +if (out.isButton()) continue; +const name = out.fields.getTextInputValue("text") +*/ + +const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise => { + let closed = false; + let cancelled = false; + const originalObject = Object.fromEntries(Object.entries(currentObject).map(([k, v]) => [k, {...v}])); + let newChannel: string | undefined; + let newChannelName: string = "{memberCount:all}-members"; + let newChannelEnabled: boolean = true; + do { + await interaction.editReply({ + embeds: [new EmojiEmbed() + .setTitle("Stats Channel") + .setDescription( + `New stats channel` + (newChannel ? ` in <#${newChannel}>` : "") + "\n\n" + + `**Name:** \`${newChannelName}\`\n` + + `**Preview:** ${await convertCurlyBracketString(newChannelName, interaction.user!.id, interaction.user.username, interaction.guild!.name, interaction.guild!.members)}\n` + + `**Enabled:** ${newChannelEnabled ? "Yes" : "No"}\n\n` + ) + .setEmoji("SETTINGS.STATS.GREEN") + .setStatus("Success") + ], components: [ + new ActionRowBuilder().addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + ), + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Cancel") + .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) + .setStyle(ButtonStyle.Danger) + .setCustomId("back"), + new ButtonBuilder() + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id")) + .setStyle(ButtonStyle.Success) + .setCustomId("save"), + new ButtonBuilder() + .setLabel("Edit name") + .setEmoji(getEmojiByName("ICONS.EDIT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("editName"), + new ButtonBuilder() + .setLabel(newChannelEnabled ? "Enabled" : "Disabled") + .setEmoji(getEmojiByName(newChannelEnabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id")) + .setStyle(ButtonStyle.Secondary) + .setCustomId("toggleEnabled") + ) + ] + }); + let i: ButtonInteraction | ChannelSelectMenuInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }}) as ButtonInteraction | ChannelSelectMenuInteraction; + } catch (e) { + closed = true; + cancelled = true; + break; + } + if (i.isButton()) { + switch (i.customId) { + case "back": + if(!i.deferred) await i.deferUpdate(); + closed = true; + break; + case "save": + if(!i.deferred) await i.deferUpdate(); //I'm lost... + if (newChannel) { + currentObject[newChannel] = { + name: newChannelName, + enabled: newChannelEnabled + } + } + closed = true; + break; + case "editName": + await interaction.editReply({ + embeds: [new EmojiEmbed() + .setTitle("Stats Channel") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + .setEmoji("SETTINGS.STATS.GREEN") + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Back") + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("back") + ) + ] + }); + showModal(i, {name: newChannelName, enabled: newChannelEnabled}) + + const out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; + if (!out) continue; + if (!out.fields) continue; + if (out.isButton()) continue; + newChannelName = out.fields.getTextInputValue("text"); + break; + case "toggleEnabled": + if(!i.deferred) await i.deferUpdate(); + newChannelEnabled = !newChannelEnabled; + break; + } + } else { + if(!i.deferred) await i.deferUpdate(); + if (i.customId === "channel") { + newChannel = i.values[0]; + } + } + } while (!closed) + if (cancelled) return originalObject; + if (!(newChannel && newChannelName && newChannelEnabled)) return originalObject; + return currentObject; // check 157 +} const callback = async (interaction: CommandInteraction) => { if (!interaction.guild) return; const { renderChannel } = client.logger; @@ -78,7 +216,7 @@ const callback = async (interaction: CommandInteraction) => { let page = 0; let closed = false; const config = await client.database.guilds.read(interaction.guild.id); - const currentObject = config.stats; + let currentObject: ObjectSchema = config.stats; let modified = false; do { let embed = new EmojiEmbed() @@ -209,6 +347,20 @@ const callback = async (interaction: CommandInteraction) => { ) ] }); + let out: Discord.ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; + } catch (e) { + continue; + } + if (!out) continue + if (!out.fields) continue + if (out.isButton()) continue; + currentObject[Object.keys(currentObject)[page]!]!.name = out.fields.getTextInputValue("text"); break; } case "toggleEnabled": { @@ -236,6 +388,7 @@ const callback = async (interaction: CommandInteraction) => { page++; break; case "add": + currentObject = await addStatsChannel(interaction, m, currentObject); break; case "save": client.database.guilds.write(interaction.guild.id, {stats: currentObject}); diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index 1a57a85..64e87b8 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -58,8 +58,9 @@ export async function modalInteractionCollector( filter: (i: MessageComponentInteraction) => interactionFilter(i), time: 300000 }) - .on("collect", (i: ButtonInteraction) => { + .on("collect", async (i: ButtonInteraction) => { mod.stop(); + if (!i.deferred) await i.deferUpdate(); resolve(i); }); const mod = new InteractionCollector(client as Client, { @@ -67,12 +68,11 @@ export async function modalInteractionCollector( time: 300000 }).on("collect", async (i: ModalSubmitInteraction) => { int.stop(); - await i.deferUpdate(); + if (!i.deferred) await i.deferUpdate(); resolve(i); }); }); } catch (e) { - console.log(e); return null; } return out; From 267563aa1ad4f53342c29cae94ed90d9de1804ac Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 21 Jan 2023 17:00:57 -0500 Subject: [PATCH 08/74] Nucleus stats done / premium started --- .gitignore | 2 +- ...orisationTest.ts => categorizationTest.ts} | 50 +++++++------- src/actions/roleMenu.ts | 4 +- src/actions/tickets/create.ts | 2 +- src/commands/mod/about.ts | 9 ++- src/commands/mod/mute.ts | 2 +- src/commands/mod/purge.ts | 6 +- src/commands/mod/viewas.ts | 4 +- src/commands/mod/warn.ts | 2 +- src/commands/nucleus/premium.ts | 69 ++++++++++++++++--- src/commands/privacy.ts | 7 +- src/commands/settings/commands.ts | 8 +-- src/commands/settings/logs/attachment.ts | 4 +- src/commands/settings/logs/channel.ts | 4 +- src/commands/settings/logs/events.ts | 6 +- src/commands/settings/logs/staff.ts | 4 +- src/commands/settings/oldStats.ts | 10 +-- src/commands/settings/stats.ts | 48 +++++-------- src/commands/settings/tickets.ts | 22 +++--- src/commands/settings/verify.ts | 12 ++-- src/commands/settings/welcome.ts | 4 +- src/commands/tags/list.ts | 4 +- src/commands/user/about.ts | 4 +- src/commands/user/track.ts | 2 +- src/context/messages/purgeto.ts | 4 +- src/events/messageEdit.ts | 2 +- src/events/roleUpdate.ts | 3 +- src/premium/createTranscript.ts | 4 +- src/reflex/guide.ts | 2 +- src/utils/confirmationMessage.ts | 4 +- src/utils/database.ts | 18 ++++- src/utils/dualCollector.ts | 8 ++- 32 files changed, 196 insertions(+), 138 deletions(-) rename src/Unfinished/{categorisationTest.ts => categorizationTest.ts} (72%) diff --git a/.gitignore b/.gitignore index db17304..d731f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ src/utils/temp/*.jpeg src/utils/temp/*.jpg ClicksMigratingProblems/oldData/ -ClicksMigratingProblems/oldData copy/ +ClicksMigratingProblems/oldData copy/ \ No newline at end of file diff --git a/src/Unfinished/categorisationTest.ts b/src/Unfinished/categorizationTest.ts similarity index 72% rename from src/Unfinished/categorisationTest.ts rename to src/Unfinished/categorizationTest.ts index dc38dfe..cbd9924 100644 --- a/src/Unfinished/categorisationTest.ts +++ b/src/Unfinished/categorizationTest.ts @@ -1,28 +1,30 @@ import { LoadingEmbed } from "../utils/defaults.js"; -import { CommandInteraction, GuildChannel, ActionRowBuilder, ButtonBuilder, SelectMenuBuilder, ButtonStyle } from "discord.js"; +import { CommandInteraction, GuildChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, StringSelectMenuBuilder, APIMessageComponentEmoji } from "discord.js"; import { SlashCommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import client from "../utils/client.js"; import addPlural from "../utils/plurals.js"; import getEmojiByName from "../utils/getEmojiByName.js"; -const command = new SlashCommandBuilder().setName("categorise").setDescription("Categorises your servers channels"); +const command = new SlashCommandBuilder().setName("categorize").setDescription("Categorizes your servers channels"); const callback = async (interaction: CommandInteraction): Promise => { - const channels = interaction.guild.channels.cache.filter((c) => c.type !== "GUILD_CATEGORY"); - const categorised = {}; + const channels = interaction.guild!.channels.cache.filter((c) => c.type !== ChannelType.GuildCategory); + const categorized = {}; await interaction.reply({ embeds: LoadingEmbed, ephemeral: true }); const predicted = {}; const types = { - general: ["general", "muted", "main", "topic", "discuss"], + important: ["rule", "announcement", "alert", "info"], + general: ["general", "main", "topic", "discuss"], commands: ["bot", "command", "music"], - images: ["pic", "selfies", "image"], - nsfw: ["porn", "nsfw", "sex"], - links: ["links"], - advertising: ["ads", "advert", "server", "partner"], - staff: ["staff", "mod", "admin"], - spam: ["spam"], - other: ["random"] + images: ["pic", "selfies", "image", "gallery", "meme", "media"], + nsfw: ["porn", "nsfw", "sex", "lewd", "fetish"], + links: ["link"], + advertising: ["ads", "advert", "partner", "bump"], + staff: ["staff", "mod", "admin", "helper", "train"], + spam: ["spam", "count"], + logs: ["log"], + other: ["random", "starboard"], }; for (const c of channels.values()) { for (const type in types) { @@ -38,14 +40,14 @@ const callback = async (interaction: CommandInteraction): Promise => { for (const c of channels) { // convert channel to a channel if its a string let channel: string | GuildChannel; - if (typeof c === "string") channel = interaction.guild.channels.cache.get(channel as string).id; + if (typeof c === "string") channel = interaction.guild!.channels.cache.get(c as string)!.id; else channel = (c[0] as unknown as GuildChannel).id; console.log(channel); if (!predicted[channel]) predicted[channel] = []; m = await interaction.editReply({ embeds: [ new EmojiEmbed() - .setTitle("Categorise") + .setTitle("Categorize") .setDescription( `Select all types that apply to <#${channel}>.\n\n` + `${addPlural(predicted[channel].length, "Suggestion")}: ${predicted[channel].join(", ")}` @@ -54,8 +56,8 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStatus("Success") ], components: [ - new ActionRowBuilder().addComponents([ - new SelectMenuBuilder() + new ActionRowBuilder().addComponents([ + new StringSelectMenuBuilder() .setCustomId("selected") .setMaxValues(Object.keys(types).length) .setMinValues(1) @@ -67,18 +69,18 @@ const callback = async (interaction: CommandInteraction): Promise => { })) ) ]), - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new ButtonBuilder() .setLabel("Accept Suggestion") .setCustomId("accept") .setStyle(ButtonStyle.Success) .setDisabled(predicted[channel].length === 0) - .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.TICK", "id"))), + .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.TICK", "id")) as APIMessageComponentEmoji), new ButtonBuilder() .setLabel('Use "Other"') .setCustomId("reject") .setStyle(ButtonStyle.Secondary) - .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.CROSS", "id"))) + .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.CROSS", "id")) as APIMessageComponentEmoji) ]) ] }); @@ -86,13 +88,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id} }); } catch (e) { return await interaction.editReply({ embeds: [ new EmojiEmbed() - .setTitle("Categorise") + .setTitle("Categorize") .setEmoji("CHANNEL.CATEGORY.DELETE") .setStatus("Danger") .setDescription( @@ -105,7 +107,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ] }); } - i.deferUpdate(); + await i.deferUpdate(); let selected; if (i.customId === "select") { selected = i.values; @@ -116,9 +118,9 @@ const callback = async (interaction: CommandInteraction): Promise => { if (i.customId === "reject") { selected = ["other"]; } - categorised[channel] = selected; + categorized[channel] = selected; } - console.log(categorised); + console.log(categorized); }; const check = () => { diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts index 31a9fcf..732c94d 100644 --- a/src/actions/roleMenu.ts +++ b/src/actions/roleMenu.ts @@ -124,7 +124,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti try { component = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id} }); } catch (e) { return; @@ -199,7 +199,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti try { component = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id} }); } catch (e) { return; diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts index 3c2dd2c..8a021d4 100644 --- a/src/actions/tickets/create.ts +++ b/src/actions/tickets/create.ts @@ -106,7 +106,7 @@ export default async function (interaction: CommandInteraction | ButtonInteracti try { component = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { return; diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts index 1f53afb..4277918 100644 --- a/src/commands/mod/about.ts +++ b/src/commands/mod/about.ts @@ -10,7 +10,6 @@ import Discord, { MessageComponentInteraction, ModalSubmitInteraction, ButtonStyle, - StringSelectMenuInteraction, TextInputStyle, APIMessageComponentEmoji, } from "discord.js"; @@ -253,7 +252,7 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { interaction.editReply({ @@ -269,7 +268,7 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if (i.customId === "filter" && i.isStringSelectMenu()) { filteredTypes = i.values; pageIndex = null; @@ -359,7 +358,7 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; @@ -423,7 +422,7 @@ const callback = async (interaction: CommandInteraction): Promise => { continue; } } else if (i.customId === "history") { - i.deferUpdate(); + await i.deferUpdate(); if (!(await showHistory(member, interaction))) return; } } diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 86291e5..0e080c3 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -103,7 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = await m.awaitMessageComponent({ - filter: (m) => m.user.id === interaction.user.id, + filter: (i) => {return i.user.id === interaction.user.id && i.id === m.id}, time: 300000 }); } catch { diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index e6b4670..cc72efb 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -94,7 +94,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = m.awaitMessageComponent({ - filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id, + filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, time: 300000 }); } catch (e) { @@ -188,7 +188,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = await m.awaitMessageComponent({ - filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id, + filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, time: 300000 }); } catch { @@ -358,7 +358,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = await m.awaitMessageComponent({ - filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id, + filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, time: 300000 }); } catch { diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts index 4b6899c..f01834c 100644 --- a/src/commands/mod/viewas.ts +++ b/src/commands/mod/viewas.ts @@ -157,12 +157,12 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let i; try { - i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id, time: 30000}); + i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 30000}); } catch (e) { closed = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if (i.customId === "back") page--; else if (i.customId === "right") page++; else if (i.customId === "category" && i.isStringSelectMenu()) page = parseInt(i.values[0]!); diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index 38aa4ad..8f67477 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -186,7 +186,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = await m.awaitMessageComponent({ - filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id, + filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, time: 300000 }); } catch (e) { diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 745f167..2b969b9 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,26 +1,75 @@ -import type { CommandInteraction } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import client from "../../utils/client.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("premium").setDescription("Information about Nucleus Premium"); const callback = async (interaction: CommandInteraction): Promise => { - interaction.reply({ - embeds: [ + await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) + const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id) + const firstDescription = "\n\nPremium allows your server to get access to extra features, for a fixed price per month.\nThis includes:\n" + + "- Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n" + + "- Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n" + if(!member) { + interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Premium") .setDescription( - "*Nucleus Premium is currently not available.*\n\n" + - "Premium allows your server to get access to extra features, for a fixed price per month.\nThis includes:\n" + - "- Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n" + - "- Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n" + `*You are not currently in the Clicks Server. To gain access to premium please join.*` + firstDescription ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") - ], - ephemeral: true - }); + ], components: [new ActionRowBuilder().addComponents(new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel("Join").setURL("https://discord.gg/bPaNnxe"))] }); + return; + } + const dbMember = await client.database.premium.fetchTotal(interaction.user.id) + let premium; + let count = 0; + if (member.roles.cache.has("1066468879309750313")) { + premium = `You have Infinite Premium! You have been gifted this by the developers as a thank you. You can give premium to any and all servers you are in.`; + count = 200; + } else if (member.roles.cache.has("1066465491713003520")) { + premium = `You have Premium tier 1! You can give premium to ${1 - dbMember}.`; + count = 1; + } else if (member.roles.cache.has("1066439526496604194")) { + premium = `You have Premium tier 2! You can give premium to ${3 - dbMember}.`; + count = 3; + } else if (member.roles.cache.has("1066464134322978912")) { + premium = `You have Premium Mod! You already give premium to all servers you have a "manage" permission in.` + count = 3; + } + + let closed = false; + do { + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + premium + firstDescription + ) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ], + components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel("Activate Premium here") + .setCustomId("premiumActivate") + .setDisabled(count <= 0) + ) + ] + }); + + const filter = (i: any) => i.customId === "premiumActivate" && i.user.id === interaction.user.id; + const collector = interaction.channel?.awaitMessageComponent({ filter, time: 60000 }); + + } while (closed); }; const check = () => { diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index 9100302..6113cc3 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -150,14 +150,14 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } nextFooter = null; - i.deferUpdate(); + await i.deferUpdate(); if (i.customId === "left") { if (page > 0) page--; selectPaneOpen = false; @@ -180,11 +180,12 @@ const callback = async (interaction: CommandInteraction): Promise => { .setColor("Danger") .send(true); if (confirmation.cancelled) { - break; + continue; } if (confirmation.success) { client.database.guilds.delete(interaction.guild!.id); client.database.history.delete(interaction.guild!.id); + client.database.notes.delete(interaction.guild!.id); nextFooter = "All data cleared"; continue; } else { diff --git a/src/commands/settings/commands.ts b/src/commands/settings/commands.ts index 5cafcc5..f3a3538 100644 --- a/src/commands/settings/commands.ts +++ b/src/commands/settings/commands.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder, Message } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; @@ -20,7 +20,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true, fetchReply: true }); - let m; + let m: Message; let clicked = ""; if (interaction.options.get("role")) { const confirmation = await new confirmationMessage(interaction) @@ -110,7 +110,7 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; @@ -119,7 +119,7 @@ const callback = async (interaction: CommandInteraction): Promise => { type modIDs = "mute" | "kick" | "ban" | "softban" | "warn" | "role"; let chosen = moderation[i.customId as modIDs]; if ((i.component as ButtonComponent).customId === "clearMuteRole") { - i.deferUpdate(); + await i.deferUpdate(); if (clicked === "clearMuteRole") { await client.database.guilds.write(interaction.guild!.id, { "moderation.mute.role": null diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index e9e8dce..f0ecbe9 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -150,13 +150,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if ((i.component as unknown as ButtonInteraction).customId === "clear") { clicks ++; if (clicks === 2) { diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts index 0852e67..b9b593c 100644 --- a/src/commands/settings/logs/channel.ts +++ b/src/commands/settings/logs/channel.ts @@ -143,13 +143,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }) as ButtonInteraction; } catch (e) { timedOut = true; } i = i! - i.deferUpdate(); + await i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clear") { clicks ++; if (clicks === 2) { diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index 7332c7b..40b4607 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder, StringSelectMenuInteraction } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder } from "discord.js"; import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import client from "../../../utils/client.js"; @@ -76,13 +76,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if (i.isStringSelectMenu() && i.customId === "logs") { const selected = i.values; const newLogs = toHexInteger(selected.map((e: string) => Object.keys(logs)[parseInt(e)]!)); diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts index d6379f0..bba6441 100644 --- a/src/commands/settings/logs/staff.ts +++ b/src/commands/settings/logs/staff.ts @@ -147,13 +147,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clear") { clicks ++; if (clicks === 2) { diff --git a/src/commands/settings/oldStats.ts b/src/commands/settings/oldStats.ts index 475b5d3..457e705 100644 --- a/src/commands/settings/oldStats.ts +++ b/src/commands/settings/oldStats.ts @@ -139,7 +139,7 @@ const callback = async (interaction: CommandInteraction) => { }); let i: MessageComponentInteraction; try { - i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 }); + i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 30000 }); } catch (e) { closed = true; continue; @@ -148,7 +148,7 @@ const callback = async (interaction: CommandInteraction) => { switch(i.customId) { case "page": page = Object.keys(stats).indexOf(i.values[0]!); - i.deferUpdate(); + await i.deferUpdate(); break; case "action": if(!changes[currentID]) changes[currentID] = {}; @@ -243,17 +243,17 @@ const callback = async (interaction: CommandInteraction) => { break; case "delete": changes[currentID] = {}; - i.deferUpdate(); + await i.deferUpdate(); break; case "toggleEnabled": changes[currentID]!.enabled = !stats[currentID]!.enabled; - i.deferUpdate(); + await i.deferUpdate(); break; } break; } } else if (i.isButton()) { - i.deferUpdate(); + await i.deferUpdate(); switch(i.customId) { case "back": page--; diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index 7dd9cf4..2fd947b 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -73,23 +73,7 @@ const showModal = async (interaction: MessageComponentInteraction, current: { en type ObjectSchema = Record -/* -let out: Discord.ModalSubmitInteraction | null = null; -try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; -} catch (e) { - continue; -} -if (!out) continue -out = out!; -if (!out.fields) continue; -if (out.isButton()) continue; -const name = out.fields.getTextInputValue("text") -*/ + const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise => { let closed = false; @@ -99,7 +83,7 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr let newChannelName: string = "{memberCount:all}-members"; let newChannelEnabled: boolean = true; do { - await interaction.editReply({ + m = await interaction.editReply({ embeds: [new EmojiEmbed() .setTitle("Stats Channel") .setDescription( @@ -114,6 +98,7 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr new ActionRowBuilder().addComponents( new ChannelSelectMenuBuilder() .setCustomId("channel") + .setPlaceholder("Select a channel to use") ), new ActionRowBuilder().addComponents( new ButtonBuilder() @@ -141,7 +126,9 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr }); let i: ButtonInteraction | ChannelSelectMenuInteraction; try { - i = await m.awaitMessageComponent({ time: 300000, filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }}) as ButtonInteraction | ChannelSelectMenuInteraction; + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => { + return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id; + }}) as ButtonInteraction | ChannelSelectMenuInteraction; } catch (e) { closed = true; cancelled = true; @@ -150,11 +137,11 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr if (i.isButton()) { switch (i.customId) { case "back": - if(!i.deferred) await i.deferUpdate(); + await i.deferUpdate(); closed = true; break; case "save": - if(!i.deferred) await i.deferUpdate(); //I'm lost... + await i.deferUpdate(); if (newChannel) { currentObject[newChannel] = { name: newChannelName, @@ -185,8 +172,8 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr const out = await modalInteractionCollector( m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true + (m) => m.channel!.id === interaction.channel!.id && m.user!.id === interaction.user!.id, + (i) => i.channel!.id === interaction.channel!.id && i.user!.id === interaction.user!.id && i.message!.id === m.id ) as Discord.ModalSubmitInteraction | null; if (!out) continue; if (!out.fields) continue; @@ -194,12 +181,12 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr newChannelName = out.fields.getTextInputValue("text"); break; case "toggleEnabled": - if(!i.deferred) await i.deferUpdate(); + await i.deferUpdate(); newChannelEnabled = !newChannelEnabled; break; } } else { - if(!i.deferred) await i.deferUpdate(); + await i.deferUpdate(); if (i.customId === "channel") { newChannel = i.values[0]; } @@ -207,7 +194,7 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr } while (!closed) if (cancelled) return originalObject; if (!(newChannel && newChannelName && newChannelEnabled)) return originalObject; - return currentObject; // check 157 + return currentObject; } const callback = async (interaction: CommandInteraction) => { if (!interaction.guild) return; @@ -322,6 +309,7 @@ const callback = async (interaction: CommandInteraction) => { if(i.isStringSelectMenu()) { switch(i.customId) { case "page": + await i.deferUpdate(); page = Object.keys(currentObject).indexOf(i.values[0]!); break; case "action": @@ -364,14 +352,15 @@ const callback = async (interaction: CommandInteraction) => { break; } case "toggleEnabled": { - i.deferUpdate(); + await i.deferUpdate(); currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled; modified = true; break; } case "delete": { - i.deferUpdate(); + await i.deferUpdate(); delete currentObject[Object.keys(currentObject)[page]!]; + page = Math.min(page, Object.keys(currentObject).length - 1); modified = true; break; } @@ -379,7 +368,7 @@ const callback = async (interaction: CommandInteraction) => { break; } } else { - i.deferUpdate(); + await i.deferUpdate(); switch(i.customId) { case "back": page--; @@ -389,6 +378,7 @@ const callback = async (interaction: CommandInteraction) => { break; case "add": currentObject = await addStatsChannel(interaction, m, currentObject); + page = Object.keys(currentObject).length - 1; break; case "save": client.database.guilds.write(interaction.guild.id, {stats: currentObject}); diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 8f9f688..6c74939 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -279,13 +279,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clearCategory") { if (lastClicked === "cat") { lastClicked = ""; @@ -382,14 +382,14 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { innerTimedOut = true; continue; } if (i.isStringSelectMenu() && i.customId === "template") { - i.deferUpdate(); + await i.deferUpdate(); await interaction.channel!.send({ embeds: [ new EmojiEmbed() @@ -413,7 +413,7 @@ const callback = async (interaction: CommandInteraction): Promise => { templateSelected = true; continue; } else if ((i.component as ButtonComponent).customId === "blank") { - i.deferUpdate(); + await i.deferUpdate(); await interaction.channel!.send({ components: [ new ActionRowBuilder().addComponents([ @@ -635,21 +635,21 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } if (i.isStringSelectMenu() && i.customId === "types") { - i.deferUpdate(); + await i.deferUpdate(); const types = toHexInteger(i.values, ticketTypes); await client.database.guilds.write(interaction.guild!.id, { "tickets.types": types }); data.types = types; } else if (i.isStringSelectMenu() && i.customId === "removeTypes") { - i.deferUpdate(); + await i.deferUpdate(); const types = i.values; let customTypes = data.customTypes; if (customTypes) { @@ -723,15 +723,15 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t data.customTypes.push(toAdd); } } else if ((i.component as ButtonComponent).customId === "switchToDefault") { - i.deferUpdate(); + await i.deferUpdate(); await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": false }, []); data.useCustom = false; } else if ((i.component as ButtonComponent).customId === "switchToCustom") { - i.deferUpdate(); + await i.deferUpdate(); await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": true }, []); data.useCustom = true; } else { - i.deferUpdate(); + await i.deferUpdate(); backPressed = true; } } diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index d3971a8..23fc99b 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -160,13 +160,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if ((i.component as ButtonComponent).customId === "clear") { clicks ++; if (clicks === 2) { @@ -249,14 +249,14 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { innerTimedOut = true; continue; } if (i.isStringSelectMenu() && i.customId === "template") { - i.deferUpdate(); + await i.deferUpdate(); await interaction.channel!.send({ embeds: [ new EmojiEmbed() @@ -280,7 +280,7 @@ const callback = async (interaction: CommandInteraction): Promise => { templateSelected = true; continue; } else if ((i.component as ButtonComponent).customId === "blank") { - i.deferUpdate(); + await i.deferUpdate(); await interaction.channel!.send({ components: [ new ActionRowBuilder().addComponents([ @@ -375,7 +375,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } } else { - i.deferUpdate(); + await i.deferUpdate(); break; } } diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index abedec5..fcd0f76 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -245,13 +245,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if (i.customId == "clear-message") { if (lastClicked == "clear-message") { await client.database.guilds.write(interaction.guild!.id, { diff --git a/src/commands/tags/list.ts b/src/commands/tags/list.ts index f0563c7..80ee127 100644 --- a/src/commands/tags/list.ts +++ b/src/commands/tags/list.ts @@ -139,13 +139,13 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if ((i.component as ButtonComponent).customId === "left") { if (page > 0) page--; selectPaneOpen = false; diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts index e43ecb7..b2a3db8 100644 --- a/src/commands/user/about.ts +++ b/src/commands/user/about.ts @@ -258,13 +258,13 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if (i.customId === "left") { if (page > 0) page--; selectPaneOpen = false; diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts index b7b239b..7160436 100644 --- a/src/commands/user/track.ts +++ b/src/commands/user/track.ts @@ -169,7 +169,7 @@ const callback = async (interaction: CommandInteraction): Promise => { try { component = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); } catch (e) { timedOut = true; diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts index df52e0b..8e2cf92 100644 --- a/src/context/messages/purgeto.ts +++ b/src/context/messages/purgeto.ts @@ -13,7 +13,7 @@ const command = new ContextMenuCommandBuilder() async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise { let component; try { - component = m.awaitMessageComponent({ time: 200000, filter: (i) => i.user.id === member.id && i.channel!.id === m.channel.id }); + component = m.awaitMessageComponent({ time: 200000, filter: (i) => i.user.id === member.id && i.channel!.id === m.channel.id && i.message.id === m.id }); } catch (e) { return false; } @@ -211,7 +211,7 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { let component; try { component = await m.awaitMessageComponent({ - filter: (m) => m.user.id === interaction.user.id && m.channel!.id === interaction.channel!.id, + filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, time: 300000 }); } catch { diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts index 20624fe..caa00f6 100644 --- a/src/events/messageEdit.ts +++ b/src/events/messageEdit.ts @@ -18,7 +18,7 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe if (config) { attachmentJump = ` [[View attachments]](${config})`; } - if (newContent === oldContent && newMessage.attachments.size === oldMessage.attachments.size) { + if (newMessage.crosspostable !== oldMessage.crosspostable) { if (!replyTo) { const data = { meta: { diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts index 924ec3e..a728b79 100644 --- a/src/events/roleUpdate.ts +++ b/src/events/roleUpdate.ts @@ -8,7 +8,8 @@ export async function callback(client: NucleusClient, oldRole: Role, newRole: Ro const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger; const auditLog = (await getAuditLog(newRole.guild as Guild, AuditLogEvent.RoleUpdate)) - .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === newRole.id)[0]!; + .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === newRole.id)[0]; + if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; const changes: Record> = { diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 04fdc08..754a06b 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -115,9 +115,9 @@ export default async function (interaction: CommandInteraction | MessageComponen try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id } + filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); - i.deferUpdate(); + await i.deferUpdate(); } catch { return; } diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts index 3971ad9..e78c0dc 100644 --- a/src/reflex/guide.ts +++ b/src/reflex/guide.ts @@ -273,7 +273,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { timedOut = true; continue; } - i.deferUpdate(); + await i.deferUpdate(); if (!("customId" in i.component)) { continue; } else if (i.component.customId === "left") { diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 613b48c..7d54674 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -183,7 +183,7 @@ class confirmationMessage { let component; try { component = await m.awaitMessageComponent({ - filter: (m) => m.user.id === this.interaction.user.id && m.channel!.id === this.interaction.channel!.id, + filter: (i) => i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id && i.id === m.id, time: 300000 }); } catch (e) { @@ -287,7 +287,7 @@ class confirmationMessage { await this.interaction.editReply({ embeds: [new EmojiEmbed() .setTitle(this.title) - .setDescription(this.failedMessage ?? "") + .setDescription(this.failedMessage ?? "*Message timed out*") .setStatus(this.failedStatus ?? "Danger") .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji) ], components: [] diff --git a/src/utils/database.ts b/src/utils/database.ts index 1e8e990..3d4ca78 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -67,7 +67,6 @@ export class Guilds { value: any, innerKey?: string | null ) { - console.log(Array.isArray(value)); if (innerKey) { await this.guilds.updateOne( { id: guild }, @@ -191,6 +190,10 @@ export class ModNotes { const entry = await this.modNotes.findOne({ guild: guild, user: user }); return entry?.note ?? null; } + + async delete(guild: string) { + await this.modNotes.deleteMany({ guild: guild }); + } } export class Premium { @@ -204,7 +207,18 @@ export class Premium { const entry = await this.premium.findOne({ appliesTo: { $in: [guild] } }); - return entry !== null; + if (!entry) return false; + return entry.expires.valueOf() > Date.now(); + } + + async fetchTotal(user: string): Promise { + const entry = await this.premium.findOne({ user: user }); + if (!entry) return 0; + return entry.appliesTo.length; + } + + setPremium(user: string, guild: string) { + return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true }); } } diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index 64e87b8..f55716e 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -60,15 +60,17 @@ export async function modalInteractionCollector( }) .on("collect", async (i: ButtonInteraction) => { mod.stop(); - if (!i.deferred) await i.deferUpdate(); + int.stop(); + await i.deferUpdate(); resolve(i); }); const mod = new InteractionCollector(client as Client, { - filter: (i: Interaction) => modalFilter(i), + filter: (i: Interaction) => modalFilter(i) && i.isModalSubmit(), time: 300000 }).on("collect", async (i: ModalSubmitInteraction) => { int.stop(); - if (!i.deferred) await i.deferUpdate(); + mod.stop(); + await i.deferUpdate(); resolve(i); }); }); From a35b71b85929b91059bef27f2dcc931b55e14e1a Mon Sep 17 00:00:00 2001 From: PineaFan Date: Tue, 24 Jan 2023 19:33:27 +0000 Subject: [PATCH 09/74] Might have changed stuff --- src/commands/nucleus/premium.ts | 2 +- src/commands/settings/oldStats.ts | 8 ++++---- src/commands/settings/stats.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 2b969b9..74d7da8 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -42,7 +42,7 @@ const callback = async (interaction: CommandInteraction): Promise => { count = 3; } - let closed = false; + const closed = false; do { interaction.editReply({ embeds: [ diff --git a/src/commands/settings/oldStats.ts b/src/commands/settings/oldStats.ts index 457e705..94b09a4 100644 --- a/src/commands/settings/oldStats.ts +++ b/src/commands/settings/oldStats.ts @@ -47,13 +47,13 @@ const callback = async (interaction: CommandInteraction) => { enabled: false }; let description = ""; - let pageSelect = new StringSelectMenuBuilder() + const pageSelect = new StringSelectMenuBuilder() .setCustomId("page") .setPlaceholder("Select a stats channel to manage") .setDisabled(Object.keys(stats).length === 0) .setMinValues(1) .setMaxValues(1); - let actionSelect = new StringSelectMenuBuilder() + const actionSelect = new StringSelectMenuBuilder() .setCustomId("action") .setPlaceholder("Perform an action") .setMinValues(1) @@ -123,7 +123,7 @@ const callback = async (interaction: CommandInteraction) => { .setDisabled(Object.keys(changes).length === 0), ); - let embed = new EmojiEmbed() + const embed = new EmojiEmbed() .setTitle("Stats Channels") .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page)) .setEmoji("SETTINGS.STATS.GREEN") @@ -264,7 +264,7 @@ const callback = async (interaction: CommandInteraction) => { case "add": break; case "save": - let changed = applyChanges(config.stats, changes); + const changed = applyChanges(config.stats, changes); singleNotify("statsChannelDeleted", interaction.guild.id, true) config.stats = changed; changes = {} diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index 2fd947b..f191a34 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -206,7 +206,7 @@ const callback = async (interaction: CommandInteraction) => { let currentObject: ObjectSchema = config.stats; let modified = false; do { - let embed = new EmojiEmbed() + const embed = new EmojiEmbed() .setTitle("Stats Settings") .setEmoji("SETTINGS.STATS.GREEN") .setStatus("Success"); From fc420b79d04da350c5c453bf332956c31b12b8b8 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 24 Jan 2023 17:14:38 -0500 Subject: [PATCH 10/74] edited premium --- src/commands/nucleus/premium.ts | 58 +++++++++++++++++++++++++++---- src/commands/settings/oldStats.ts | 2 +- src/index.ts | 5 ++- src/utils/database.ts | 6 +++- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 74d7da8..8b47ab7 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -42,14 +42,20 @@ const callback = async (interaction: CommandInteraction): Promise => { count = 3; } - const closed = false; + const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); + let premiumGuild = "" + if (hasPremium) { + premiumGuild = `\n\n**This server has premium!**` + } + + let closed = false; do { interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Premium") .setDescription( - premium + firstDescription + premium + firstDescription + premiumGuild ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") @@ -61,15 +67,55 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStyle(ButtonStyle.Primary) .setLabel("Activate Premium here") .setCustomId("premiumActivate") - .setDisabled(count <= 0) + .setDisabled(count <= 0 && hasPremium) ) ] }); const filter = (i: any) => i.customId === "premiumActivate" && i.user.id === interaction.user.id; - const collector = interaction.channel?.awaitMessageComponent({ filter, time: 60000 }); - - } while (closed); + let i; + try { + i = await interaction.channel!.awaitMessageComponent({ filter, time: 60000 }); + } catch (e) { + return; + } + if (i) { + i.deferUpdate(); + let guild = i.guild!; + let m = await client.database.premium.fetchTotal(interaction.user.id); + if (count - m <= 0) { + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + `You have already activated premium on the maximum amount of servers!` + firstDescription + ) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ], + components: [] + }); + closed = true; + } else { + client.database.premium.addPremium(interaction.user.id, guild.id); + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + `You have activated premium on this server!` + firstDescription + ) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ], + components: [] + }); + closed = true; + } + } + + } while (!closed); }; const check = () => { diff --git a/src/commands/settings/oldStats.ts b/src/commands/settings/oldStats.ts index 94b09a4..8f13109 100644 --- a/src/commands/settings/oldStats.ts +++ b/src/commands/settings/oldStats.ts @@ -12,7 +12,7 @@ import type { GuildConfig } from "../../utils/database.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder - .setName("stats") + .setName("oldstats") .setDescription("Controls channels which update when someone joins or leaves the server") type ChangesType = Record diff --git a/src/index.ts b/src/index.ts index 362b805..a88cc54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,4 +14,7 @@ process.on("uncaughtException", (err) => { console.error(err) }); await client.login(config.enableDevelopment ? config.developmentToken : config.token) -await recordPerformance(); \ No newline at end of file +await recordPerformance(); + +import { getCommandMentionByName} from "./utils/getCommandMentionByName.js"; +console.log(await getCommandMentionByName("nucleus/premium")) diff --git a/src/utils/database.ts b/src/utils/database.ts index 3d4ca78..10b0ddb 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -217,9 +217,13 @@ export class Premium { return entry.appliesTo.length; } - setPremium(user: string, guild: string) { + addPremium(user: string, guild: string) { return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true }); } + + removePremium(user: string, guild: string) { + return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } }); + } } export interface GuildConfig { From 1c3ad3ce4be53a12ee5df734575a613180ba90d7 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 25 Jan 2023 17:58:36 -0500 Subject: [PATCH 11/74] worked on settings/rolemenu and help --- TODO | 14 +- TODO.json | 19 +- src/actions/roleMenu.ts | 52 ++-- src/commands/help.ts | 86 +++++- src/commands/nucleus/premium.ts | 2 +- src/commands/settings/oldStats.ts | 291 ------------------ src/commands/settings/rolemenu.ts | 344 +++++++++++++++++++++- src/commands/settings/stats.ts | 47 ++- src/config/emojis.json | 3 + src/index.ts | 8 +- src/reflex/guide.ts | 2 +- src/utils/client.ts | 4 +- src/utils/commandRegistration/register.ts | 1 + src/utils/confirmationMessage.ts | 7 +- 14 files changed, 508 insertions(+), 372 deletions(-) delete mode 100644 src/commands/settings/oldStats.ts diff --git a/TODO b/TODO index d2cd1a2..2ab95dc 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,4 @@ -Role all +? Role all Server rules verificationRequired on welcome // TODO !IMPORTANT! URL + image hash + file hash database - -ROLE MENU SETTINGS - -**Title** -> Description -name: role -name: role -name: role - -[ Select an option to remove ] -[ Reorder roles ] -< Previous page | Add role | Delete page | Add page > diff --git a/TODO.json b/TODO.json index 6f0b94e..19d077c 100644 --- a/TODO.json +++ b/TODO.json @@ -22,6 +22,21 @@ "roles": true } }, - "roleMenu": [], - "tracks": [] + "tracks": [], + "rolemenu": { + "enabled": false, + "allowWebUI": false, + "options": [ + { + "name": false, + "description": false, + "options": [ + { + "name": false, + "description": false + } + ] + } + ] + } } diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts index 732c94d..16689b7 100644 --- a/src/actions/roleMenu.ts +++ b/src/actions/roleMenu.ts @@ -30,6 +30,36 @@ export interface RoleMenuSchema { interaction: CommandInteraction | ButtonInteraction | ContextMenuCommandInteraction; } +interface ObjectSchema { + name: string; + description: string; + min: number; + max: number; + options: { + name: string; + description: string | null; + role: string; + }[]; +} + +export const configToDropdown = (placeholder: string, currentPageData: ObjectSchema, selectedRoles?: string[]): ActionRowBuilder => { + return new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("roles") + .setPlaceholder(placeholder) + .setMinValues(currentPageData.min) + .setMaxValues(currentPageData.max) + .addOptions(currentPageData.options.map((option: {name: string; description: string | null; role: string;}) => { + const builder = new StringSelectMenuOptionBuilder() + .setLabel(option.name) + .setValue(option.role) + .setDefault(selectedRoles ? selectedRoles.includes(option.role) : false); + if (option.description) builder.setDescription(option.description); + return builder; + })) + ) +} + export async function callback(interaction: CommandInteraction | ButtonInteraction) { if (!interaction.member) return; if (!interaction.guild) return; @@ -56,7 +86,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti ], ephemeral: true }); - const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true }); + const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); if (config.roleMenu.allowWebUI) { // TODO: Make rolemenu web ui const loginMethods: {webUI: boolean} = { webUI: false @@ -124,9 +154,10 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti try { component = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id} + filter: (i) => { return i.user.id === interaction.user.id && i.channelId === interaction.channelId && i.message.id === m.id} }); } catch (e) { + console.log(e); return; } component.deferUpdate(); @@ -175,21 +206,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti .setCustomId("done") .setDisabled(!complete) ), - new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("roles") - .setPlaceholder("Select...") - .setMinValues(currentPageData.min) - .setMaxValues(currentPageData.max) - .addOptions(currentPageData.options.map((option) => { - const builder = new StringSelectMenuOptionBuilder() - .setLabel(option.name) - .setValue(option.role) - .setDefault(selectedRoles[page]!.includes(option.role)); - if (option.description) builder.setDescription(option.description); - return builder; - })) - ) + configToDropdown("Select...", currentPageData, selectedRoles[page]) ]; await interaction.editReply({ embeds: [embed], @@ -202,6 +219,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id} }); } catch (e) { + console.log(e); return; } component.deferUpdate(); diff --git a/src/commands/help.ts b/src/commands/help.ts index 767ca46..1a0ce16 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,17 +1,89 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; +import { ActionRowBuilder, CommandInteraction, StringSelectMenuBuilder, ApplicationCommand, ApplicationCommandOptionType } from "discord.js"; import { SlashCommandBuilder } from "@discordjs/builders"; +import client from "../utils/client.js"; +import EmojiEmbed from "../utils/generateEmojiEmbed.js"; +import { LoadingEmbed } from "../utils/defaults.js"; const command = new SlashCommandBuilder() .setName("help") .setDescription("Shows help for commands"); const callback = async (interaction: CommandInteraction): Promise => { - interaction.reply({components: [new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Create ticket") - .setStyle(ButtonStyle.Primary) - .setCustomId("createticket") - )]}); // TODO: FINISH THIS FOR RELEASE + const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true }); + const commands = client.fetchedCommands; + + const commandRow = new ActionRowBuilder() + .addComponents( + commands.map((command) => { + return new StringSelectMenuBuilder() + .setCustomId(command.name) + .setPlaceholder("Select a command") + .addOptions({ + label: command.name, + value: command.name + }) + }) + ); + + let closed = false; + do { + let current: ApplicationCommand | undefined; + const subcommandGroupRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("subcommandGroupRow") + ); + const subcommandRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("subcommandRow") + ); + const embed = new EmojiEmbed() + .setTitle("Help") + .setStatus("Success") + .setEmoji("📖") + + if(!current) { + embed.setDescription( + `**${"Help Menu Home"}**\n\n` + + `${"Select a command to get started"}` + ) + } else { + embed.setDescription( + `**${current.name}**\n\n` + + `${current.description}` + ) + if(current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup).length > 0) { + subcommandGroupRow.components[0]! + .addOptions( + current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup).map((option) => { + return { + label: option.name, + value: option.name + } + }) + ) + } else { + if(current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand).length > 0) { + subcommandRow.components[0]! + .addOptions( + current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand).map((option) => { + return { + label: option.name, + value: option.name + } + }) + ) + } + } + } + let cmps = [commandRow]; + if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow); + if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow); + + await interaction.editReply({ embeds: [embed], components: cmps }); + + } while (!closed); }; const check = () => { diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 8b47ab7..e74e23c 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -91,7 +91,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( `You have already activated premium on the maximum amount of servers!` + firstDescription ) - .setEmoji("NUCLEUS.LOGO") + .setEmoji("NUCLEUS.PREMIUMACTIVATE") .setStatus("Danger") ], components: [] diff --git a/src/commands/settings/oldStats.ts b/src/commands/settings/oldStats.ts deleted file mode 100644 index 8f13109..0000000 --- a/src/commands/settings/oldStats.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js"; -import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import client from "../../utils/client.js"; -import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; -import singleNotify from "../../utils/singleNotify.js"; -import getEmojiByName from "../../utils/getEmojiByName.js"; -import createPageIndicator from "../../utils/createPageIndicator.js"; -import { modalInteractionCollector } from "../../utils/dualCollector.js"; -import type { GuildConfig } from "../../utils/database.js"; - -const command = (builder: SlashCommandSubcommandBuilder) => - builder - .setName("oldstats") - .setDescription("Controls channels which update when someone joins or leaves the server") - -type ChangesType = Record - -const applyChanges = (baseObject: GuildConfig['stats'], changes: ChangesType): GuildConfig['stats'] => { - for (const [id, { name, enabled }] of Object.entries(changes)) { - if (!baseObject[id]) baseObject[id] = { name: "", enabled: false}; - if (name) baseObject[id]!.name = name; - if (enabled) baseObject[id]!.enabled = enabled; - } - return baseObject; -} - - -const callback = async (interaction: CommandInteraction) => { - try{ - if (!interaction.guild) return; - const { renderChannel } = client.logger; - let closed = false; - let page = 0; - const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); - let changes: ChangesType = {}; - do { - const config = await client.database.guilds.read(interaction.guild.id); - const stats = config.stats; - let currentID = ""; - let current: { - name: string; - enabled: boolean; - } = { - name: "", - enabled: false - }; - let description = ""; - const pageSelect = new StringSelectMenuBuilder() - .setCustomId("page") - .setPlaceholder("Select a stats channel to manage") - .setDisabled(Object.keys(stats).length === 0) - .setMinValues(1) - .setMaxValues(1); - const actionSelect = new StringSelectMenuBuilder() - .setCustomId("action") - .setPlaceholder("Perform an action") - .setMinValues(1) - .setMaxValues(1) - .setDisabled(Object.keys(stats).length === 0) - .addOptions( - new StringSelectMenuOptionBuilder() - .setLabel("Edit") - .setValue("edit") - .setDescription("Edit the name of this stats channel") - .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), - new StringSelectMenuOptionBuilder() - .setLabel("Delete") - .setValue("delete") - .setDescription("Delete this stats channel") - .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) - ); - if (Object.keys(stats).length === 0) { - description = "You do not have any stats channels set up yet" - pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No stats channels").setValue("none")) - } else { - currentID = Object.keys(stats)[page]! - current = stats[currentID]!; - current = applyChanges({ [currentID]: current }, changes)[currentID]!; - for (const [id, { name, enabled }] of Object.entries(stats)) { - pageSelect.addOptions( - new StringSelectMenuOptionBuilder() - .setLabel(name) - .setValue(id) - .setDescription(`Enabled: ${enabled}`) - ); - } - actionSelect.addOptions(new StringSelectMenuOptionBuilder() - .setLabel(current.enabled ? "Disable" : "Enable") - .setValue("toggleEnabled") - .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`) - .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji) - ); - description = `**Currently Editing:** ${renderChannel(currentID)}\n\n` + - `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` + - `**Name:** \`${current.name}\`\n` + - `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` - } - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId("back") - .setStyle(ButtonStyle.Primary) - .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) - .setDisabled(page === 0), - new ButtonBuilder() - .setCustomId("next") - .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Primary) - .setDisabled(page === Object.keys(stats).length - 1), - new ButtonBuilder() - .setCustomId("add") - .setLabel("Create new") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Secondary) - .setDisabled(Object.keys(stats).length >= 24), - new ButtonBuilder() - .setCustomId("save") - .setLabel("Save") - .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Success) - .setDisabled(Object.keys(changes).length === 0), - ); - - const embed = new EmojiEmbed() - .setTitle("Stats Channels") - .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page)) - .setEmoji("SETTINGS.STATS.GREEN") - .setStatus("Success") - - interaction.editReply({ - embeds: [embed], - components: [ - new ActionRowBuilder().addComponents(pageSelect), - new ActionRowBuilder().addComponents(actionSelect), - row - ] - }); - let i: MessageComponentInteraction; - try { - i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 30000 }); - } catch (e) { - closed = true; - continue; - } - if (i.isStringSelectMenu()) { - switch(i.customId) { - case "page": - page = Object.keys(stats).indexOf(i.values[0]!); - await i.deferUpdate(); - break; - case "action": - if(!changes[currentID]) changes[currentID] = {}; - switch(i.values[0]!) { - case "edit": - await i.showModal( - new Discord.ModalBuilder() - .setCustomId("modal") - .setTitle(`Stats channel name`) - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("ex1") - .setLabel("Server Info (1/3)") - .setPlaceholder( - `{serverName} - This server's name\n\n` + - `These placeholders will be replaced with the server's name, etc..` - ) - .setMaxLength(1) - .setRequired(false) - .setStyle(Discord.TextInputStyle.Paragraph) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("ex2") - .setLabel("Member Counts (2/3) - {MemberCount:...}") - .setPlaceholder( - `{:all} - Total member count\n` + - `{:humans} - Total non-bot users\n` + - `{:bots} - Number of bots\n` - ) - .setMaxLength(1) - .setRequired(false) - .setStyle(Discord.TextInputStyle.Paragraph) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("ex3") - .setLabel("Latest Member (3/3) - {member:...}") - .setPlaceholder( - `{:name} - The members name\n` - ) - .setMaxLength(1) - .setRequired(false) - .setStyle(Discord.TextInputStyle.Paragraph) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("text") - .setLabel("Channel name input") - .setMaxLength(1000) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Short) - .setValue(current.name) - ) - ) - ); - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Stats Channel") - .setDescription("Modal opened. If you can't see it, click back and try again.") - .setStatus("Success") - .setEmoji("SETTINGS.STATS.GREEN") - ], - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("back") - ) - ] - }); - let out: Discord.ModalSubmitInteraction | null; - try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; - } catch (e) { - continue; - } - if (!out) continue - if (!out.fields) continue - if (out.isButton()) continue; - const newString = out.fields.getTextInputValue("text"); - if (!newString) continue; - changes[currentID]!.name = newString; - break; - case "delete": - changes[currentID] = {}; - await i.deferUpdate(); - break; - case "toggleEnabled": - changes[currentID]!.enabled = !stats[currentID]!.enabled; - await i.deferUpdate(); - break; - } - break; - } - } else if (i.isButton()) { - await i.deferUpdate(); - switch(i.customId) { - case "back": - page--; - break; - case "next": - page++; - break; - case "add": - break; - case "save": - const changed = applyChanges(config.stats, changes); - singleNotify("statsChannelDeleted", interaction.guild.id, true) - config.stats = changed; - changes = {} - await client.database.guilds.write(interaction.guildId!, config); - } - } - console.log(changes, config.stats); - } while (!closed); - } catch(e) { - console.log(e) - } -}; - -const check = (interaction: CommandInteraction) => { - const member = interaction.member as Discord.GuildMember; - if (!member.permissions.has("ManageChannels")) - return "You must have the *Manage Channels* permission to use this command"; - return true; -}; - - -export { command }; -export { callback }; -export { check }; \ No newline at end of file diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index b62d962..f8fb6f4 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -1,16 +1,350 @@ import type Discord from "discord.js"; -import type { CommandInteraction } from "discord.js"; +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, ModalBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import client from "../../utils/client.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import createPageIndicator from "../../utils/createPageIndicator.js"; +import { configToDropdown } from "../../actions/roleMenu.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("rolemenu") - .setDescription("rolemenu") // TODO - .addRoleOption((option) => option.setName("role").setDescription("The role to give after verifying")); // FIXME FOR FUCK SAKE + .setDescription("rolemenu") + +interface ObjectSchema { + name: string; + description: string; + min: number; + max: number; + options: { + name: string; + description: string | null; + role: string; + }[]; +} + +const editNameDescription = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { + + let {name, description} = data; + const modal = new ModalBuilder() + .setTitle("Edit Name and Description") + .setCustomId("editNameDescription") + .addComponents( + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("name") + .setPlaceholder(name ?? "") + .setStyle(TextInputStyle.Short) + .setRequired(true), + new TextInputBuilder() + .setCustomId("description") + .setPlaceholder(description ?? "") + .setStyle(TextInputStyle.Short) + ) + ) + const button = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + + return [name, description] + +} + +const ellipsis = (str: string, max: number): string => { + if (str.length <= max) return str; + return str.slice(0, max - 3) + "..."; +} + +const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema) => { + if (!data) data = { + name: "Role Menu Page", + description: "A new role menu page", + min: 0, + max: 0, + options: [] + }; + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("addRole") + .setLabel("Add Role") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + ); + + let back = false + do { + const previewSelect = configToDropdown("Edit Roles", {name: data.name, description: data.description, min: 1, max: 1, options: data.options}); + const embed = new EmojiEmbed() + .setTitle(`${data.name}`) + .setStatus("Success") + .setDescription( + `**Description:**\n> ${data.description}\n\n` + + `**Min:** ${data.min}` + (data.min === 0 ? " (Members will be given a skip button)" : "") + "\n" + + `**Max:** ${data.max}\n` + ) + + interaction.editReply({embeds: [embed], components: [previewSelect, buttons]}); + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + back = true; + break; + } + + if (i.isStringSelectMenu()) { + if(i.customId === "roles") { + await i.deferUpdate(); + await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0])); + } + } else if (i.isButton()) { + await i.deferUpdate(); + switch (i.customId) { + case "back": + back = true; + break; + case "edit": + let [name, description] = await editNameDescription(interaction, m, data); + data.name = name ? name : data.name; + data.description = description ? description : data.description; + break; + case "addRole": + data.options.push(await createRoleMenuOptionPage(interaction, m)); + break; + } + } + + } while (!back); + return data; +} + +const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: {name: string; description: string | null; role: string}) => { + const { renderRole} = client.logger; + if (!data) data = { + name: "Role Menu Option", + description: null, + role: "No role set" + }; + let back = false; + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit Details") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji) + ); + do { + const roleSelect = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder(data.role ? "Set role to" : "Set the role"); + const embed = new EmojiEmbed() + .setTitle(`${data.name ?? "New Role Menu Option"}`) + .setStatus("Success") + .setDescription( + `**Description:**\n> ${data.description ?? "No description set"}\n\n` + + `**Role:** ${renderRole((await interaction.guild!.roles.fetch(data.role))!) ?? "No role set"}\n` + ) + + interaction.editReply({embeds: [embed], components: [new ActionRowBuilder().addComponents(roleSelect), buttons]}); + + let i: RoleSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | RoleSelectMenuInteraction; + } catch (e) { + back = true; + break; + } + + if (i.isRoleSelectMenu()) { + if(i.customId === "role") { + await i.deferUpdate(); + data.role = (i as RoleSelectMenuInteraction).values[0]!; + } + } else if (i.isButton()) { + await i.deferUpdate(); + switch (i.customId) { + case "back": + back = true; + break; + case "edit": + await i.deferUpdate(); + let [name, description] = await editNameDescription(interaction, m, data as {name: string; description: string}); + data.name = name ? name : data.name; + data.description = description ? description : data.description; + break; + } + } + } while (!back); + return data; +} const callback = async (interaction: CommandInteraction): Promise => { - console.log("we changed the charger again because fuck you"); - await interaction.reply("You're mum"); + if (!interaction.guild) return; + const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true}); + + let page = 0; + let closed = false; + const config = await client.database.guilds.read(interaction.guild.id); + let currentObject: ObjectSchema[] = config.roleMenu.options; + let modified = false; + do { + const embed = new EmojiEmbed() + .setTitle("Role Menu Settings") + .setEmoji("GUILD.GREEN") + .setStatus("Success"); + const noRoleMenus = currentObject.length === 0; + let current: ObjectSchema; + + const pageSelect = new StringSelectMenuBuilder() + .setCustomId("page") + .setPlaceholder("Select a Role Menu page to manage"); + const actionSelect = new StringSelectMenuBuilder() + .setCustomId("action") + .setPlaceholder("Perform an action") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Edit") + .setDescription("Edit this page") + .setValue("edit") + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new StringSelectMenuOptionBuilder() + .setLabel("Delete") + .setDescription("Delete this page") + .setValue("delete") + .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) + ); + const buttonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + .setDisabled(page === 0), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Primary) + .setDisabled(page === Object.keys(currentObject).length - 1), + new ButtonBuilder() + .setCustomId("add") + .setLabel("New Page") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(currentObject).length >= 24), + new ButtonBuilder() + .setCustomId("reorder") + .setLabel("Reorder Pages") + .setEmoji(getEmojiByName("ICONS.SHUFFLE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(currentObject).length <= 1), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(!modified), + ); + if(noRoleMenus) { + embed.setDescription("No role menu page have been set up yet. Use the button below to add one.\n\n" + + createPageIndicator(1, 1, undefined, true) + ); + pageSelect.setDisabled(true); + actionSelect.setDisabled(true); + pageSelect.addOptions(new StringSelectMenuOptionBuilder() + .setLabel("No role menu pages") + .setValue("none") + ); + } else { + page = Math.min(page, Object.keys(currentObject).length - 1); + current = currentObject[page]!; + embed.setDescription(`**Currently Editing:** ${current.name}\n\n` + + `**Description:** \`${current.description}\`\n` + + `\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}` + ); + + pageSelect.addOptions( + currentObject.map((key: ObjectSchema, index) => { + return new StringSelectMenuOptionBuilder() + .setLabel(ellipsis(key.name, 50)) + .setDescription(ellipsis(key.description, 50)) + .setValue(index.toString()); + }) + ); + + } + + await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder().addComponents(actionSelect), new ActionRowBuilder().addComponents(pageSelect), buttonRow]}); + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + + await i.deferUpdate(); + if (i.isButton()) { + switch (i.customId) { + case "back": + page--; + break; + case "next": + page++; + break; + case "add": + currentObject.push(await createRoleMenuPage(i, m)); + page = currentObject.length - 1; + break; + case "reorder": + break; + case "save": + client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject}); + modified = false; + break; + } + } else if (i.isStringSelectMenu()) { + switch (i.customId) { + case "action": + switch(i.values[0]) { + case "edit": + currentObject[page] = await createRoleMenuPage(i, m, current!); + modified = true; + break; + case "delete": + currentObject.splice(page, 1); + break; + } + break; + case "page": + page = parseInt(i.values[0]!); + break; + } + } + + } while (!closed) }; const check = (interaction: CommandInteraction) => { diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f191a34..91da382 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -232,29 +232,29 @@ const callback = async (interaction: CommandInteraction) => { .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) ); const buttonRow = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId("back") - .setStyle(ButtonStyle.Primary) - .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) - .setDisabled(page === 0), - new ButtonBuilder() - .setCustomId("next") - .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Primary) - .setDisabled(page === Object.keys(currentObject).length - 1), - new ButtonBuilder() - .setCustomId("add") - .setLabel("Create new") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Secondary) - .setDisabled(Object.keys(currentObject).length >= 24), - new ButtonBuilder() - .setCustomId("save") - .setLabel("Save") - .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) - .setStyle(ButtonStyle.Success) - .setDisabled(modified), + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + .setDisabled(page === 0), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Primary) + .setDisabled(page === Object.keys(currentObject).length - 1), + new ButtonBuilder() + .setCustomId("add") + .setLabel("Create new") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(currentObject).length >= 24), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(modified), ); if (noStatsChannels) { embed.setDescription("No stats channels have been set up yet. Use the button below to add one.\n\n" + @@ -275,7 +275,6 @@ const callback = async (interaction: CommandInteraction) => { .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`) .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji) ); - embed.setDescription(`**Currently Editing:** ${renderChannel(Object.keys(currentObject)[page]!)}\n\n` + `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` + `**Name:** \`${current.name}\`\n` + diff --git a/src/config/emojis.json b/src/config/emojis.json index 8c45353..05a5e1d 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -1,6 +1,8 @@ { "NUCLEUS": { "LOGO": "953040840945721385", + "PREMIUMACTIVATE": "a1067536222764925068", + "PREMIUM": "1067928702027042876", "LOADING": "a946346549271732234", "INFO": { "HELP": "751751467014029322", @@ -23,6 +25,7 @@ "ATTACHMENT": "997570687193587812", "LOGGING": "999613304446144562", "SAVE": "1065722246322200586", + "SHUFFLE": "1067913930304921690", "NOTIFY": { "ON": "1000726394579464232", "OFF": "1000726363495477368" diff --git a/src/index.ts b/src/index.ts index a88cc54..b67da33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,17 +4,15 @@ import config from "./config/main.json" assert { type: "json" }; import register from "./utils/commandRegistration/register.js"; import { record as recordPerformance } from "./utils/performanceTesting/record.js"; -client.on("ready", () => { +client.on("ready", async () => { console.log(`Logged in as ${client.user!.tag}!`); register(); runServer(client); + client.fetchedCommands = await client.application?.commands.fetch()!; }); process.on("unhandledRejection", (err) => { console.error(err) }); process.on("uncaughtException", (err) => { console.error(err) }); await client.login(config.enableDevelopment ? config.developmentToken : config.token) -await recordPerformance(); - -import { getCommandMentionByName} from "./utils/getCommandMentionByName.js"; -console.log(await getCommandMentionByName("nucleus/premium")) +await recordPerformance(); \ No newline at end of file diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts index e78c0dc..668b56d 100644 --- a/src/reflex/guide.ts +++ b/src/reflex/guide.ts @@ -178,7 +178,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { "**Attachment logs**\n> When a message with attachments is edited or deleted, the logs will also include the images sent.\n" + "\nPremium is not yet available. Check `/nucleus premium` for updates on features and pricing" ) - .setEmoji("NUCLEUS.COMMANDS.LOCK") + .setEmoji("NUCLEUS.PREMIUM") .setStatus("Danger") ) .setTitle("Premium") diff --git a/src/utils/client.ts b/src/utils/client.ts index 46d9f92..a199b5b 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,4 +1,4 @@ -import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits } from 'discord.js'; +import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits, Collection } from 'discord.js'; import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; @@ -32,7 +32,7 @@ class NucleusClient extends Client { check: (interaction: Interaction) => Promise | boolean, autocomplete: (interaction: AutocompleteInteraction) => Promise }> = {}; - + fetchedCommands: Collection = new Collection(); constructor(database: typeof NucleusClient.prototype.database) { super({ intents: [ GatewayIntentBits.Guilds, diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 4a13807..abc8c39 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -213,6 +213,7 @@ export default async function register() { await client.application?.commands.set(commandList); } } + await registerCommandHandler(); await registerEvents(); console.log(`${colours.green}Registered commands, events and context menus${colours.none}`) diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 7d54674..6dc424e 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -188,8 +188,7 @@ class confirmationMessage { }); } catch (e) { success = false; - returnComponents = true; - continue; + break; } if (component.customId === "yes") { component.deferUpdate(); @@ -277,8 +276,6 @@ class confirmationMessage { } const returnValue: Awaited> = {}; - if (returnComponents || success !== undefined) returnValue.components = this.customButtons; - if (success !== undefined) returnValue.success = success; if (cancelled) { await this.timeoutError() returnValue.cancelled = true; @@ -294,6 +291,8 @@ class confirmationMessage { }); return {success: false} } + if (returnComponents || success !== undefined) returnValue.components = this.customButtons; + if (success !== undefined) returnValue.success = success; if (newReason) returnValue.newReason = newReason; const typedReturnValue = returnValue as {cancelled: true} | From 92a003ca6fc1939cb9dcb1e944c391ca2219bb27 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 25 Jan 2023 18:01:21 -0500 Subject: [PATCH 12/74] slight modification to help --- src/commands/help.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index 1a0ce16..26d86c5 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -53,10 +53,12 @@ const callback = async (interaction: CommandInteraction): Promise => { `**${current.name}**\n\n` + `${current.description}` ) - if(current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup).length > 0) { + const subcommands = current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand); + const subcommandGroups = current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup); + if(subcommandGroups.length > 0) { subcommandGroupRow.components[0]! .addOptions( - current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup).map((option) => { + subcommandGroups.map((option) => { return { label: option.name, value: option.name @@ -64,10 +66,10 @@ const callback = async (interaction: CommandInteraction): Promise => { }) ) } else { - if(current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand).length > 0) { + if(subcommands.length > 0) { subcommandRow.components[0]! .addOptions( - current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand).map((option) => { + subcommands.map((option) => { return { label: option.name, value: option.name From d471ccdfe018c8856fb99c36589e16ac52ea6e4f Mon Sep 17 00:00:00 2001 From: PineaFan Date: Thu, 26 Jan 2023 20:48:40 +0000 Subject: [PATCH 13/74] changed wording on guide and updated commandmentionbyname --- TODO.json | 18 +-- src/commands/privacy.ts | 4 +- src/commands/settings/logs/channel.ts | 2 +- src/commands/settings/logs/events.ts | 2 +- src/commands/settings/logs/staff.ts | 2 +- src/reflex/guide.ts | 136 ++++++++++------------ src/utils/client.ts | 2 + src/utils/commandRegistration/register.ts | 10 +- src/utils/getCommandMentionByName.ts | 15 +-- 9 files changed, 85 insertions(+), 106 deletions(-) diff --git a/TODO.json b/TODO.json index 19d077c..8b211ef 100644 --- a/TODO.json +++ b/TODO.json @@ -22,21 +22,5 @@ "roles": true } }, - "tracks": [], - "rolemenu": { - "enabled": false, - "allowWebUI": false, - "options": [ - { - "name": false, - "description": false, - "options": [ - { - "name": false, - "description": false - } - ] - } - ] - } + "tracks": [] } diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index 6113cc3..e20bf34 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -22,8 +22,8 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( "Nucleus is a bot that naturally needs to store data about servers.\n" + "We are entirely [open source](https://github.com/ClicksMinutePer/Nucleus), so you can check exactly what we store, and how it works.\n\n" + - "If you are a server administrator, you can view the options page in the dropdown under this message.\n\n" + - "Any questions about Nucleus, how it works and data stored can be asked in [our server](https://discord.gg/bPaNnxe)." + "If you are a server administrator, you can view the options page in the dropdown under this message.\n\n" + // TODO + "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)." ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts index b9b593c..afd7735 100644 --- a/src/commands/settings/logs/channel.ts +++ b/src/commands/settings/logs/channel.ts @@ -9,7 +9,7 @@ import client from "../../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder - .setName("channel") + .setName("general") .setDescription("Sets or shows the log channel") .addChannelOption((option) => option diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index 40b4607..1249d2b 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -31,7 +31,7 @@ const logs: Record = { const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("events").setDescription("Sets what events should be logged"); -const callback = async (interaction: CommandInteraction): Promise => { +const callback = async (interaction: CommandInteraction): Promise => { // TODO: Maybe merge this into /settings log general await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts index bba6441..ba9bbba 100644 --- a/src/commands/settings/logs/staff.ts +++ b/src/commands/settings/logs/staff.ts @@ -9,7 +9,7 @@ import client from "../../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder - .setName("staff") + .setName("warnings") .setDescription("Settings for the staff notifications channel") .addChannelOption((option) => option diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts index 668b56d..64c4f13 100644 --- a/src/reflex/guide.ts +++ b/src/reflex/guide.ts @@ -1,3 +1,4 @@ +import { getCommandMentionByName } from './../utils/getCommandMentionByName.js'; import { LoadingEmbed } from "../utils/defaults.js"; import Discord, { ActionRowBuilder, @@ -18,32 +19,43 @@ import { Embed } from "../utils/defaults.js"; export default async (guild: Guild, interaction?: CommandInteraction) => { let c: GuildTextBasedChannel | null = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel; c = c - ? c - : (guild.channels.cache.find( - (ch) => - [ - ChannelType.GuildText, - ChannelType.GuildAnnouncement, - ChannelType.PublicThread, - ChannelType.PrivateThread, - ChannelType.AnnouncementThread - ].includes(ch.type) && - ch.permissionsFor(guild.roles.everyone).has("SendMessages") && - ch.permissionsFor(guild.members.me!).has("EmbedLinks") - ) as GuildTextBasedChannel | undefined) ?? null; + ? c + : (guild.channels.cache.find( + (ch) => + [ + ChannelType.GuildText, + ChannelType.GuildAnnouncement, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ChannelType.AnnouncementThread + ].includes(ch.type) && + ch.permissionsFor(guild.roles.everyone).has("SendMessages") && + ch.permissionsFor(guild.members.me!).has("EmbedLinks") + ) as GuildTextBasedChannel | undefined) ?? null; if (interaction) c = interaction.channel as GuildTextBasedChannel; if (!c) { return; } + let m: Message; + if (interaction) { + m = (await interaction.reply({ + embeds: LoadingEmbed, + fetchReply: true, + ephemeral: true + })) as Message; + } else { + m = await c.send({ embeds: LoadingEmbed }); + } + let page = 0; const pages = [ new Embed() .setEmbed( new EmojiEmbed() .setTitle("Welcome to Nucleus") .setDescription( - "Thanks for adding Nucleus to your server\n\n" + - "On the next few pages you can find instructions on getting started, and commands you may want to set up\n\n" + - "If you need support, have questions or want features, you can let us know in [Clicks](https://discord.gg/bPaNnxe)" + "Thanks for adding Nucleus to your server!\n\n" + + "The next few pages will show what features Nucleus has to offer, and how to enable them.\n\n" + + "If you need support, have questions or want features, you can let us know in [Clicks](https://discord.gg/bPaNnxe)!" ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") @@ -54,15 +66,17 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { new Embed() .setEmbed( new EmojiEmbed() - .setTitle("Logging") + .setTitle("Logs") .setDescription( "Nucleus can log server events and keep you informed with what content is being posted to your server.\n" + "We have 2 different types of logs, which each can be configured to send to a channel of your choice:\n" + - "**General Logs:** These are events like kicks and channel changes etc.\n" + - "**Warning Logs:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member. " + - "These go to to a separate staff notifications channel.\n\n" + - "A general log channel can be set with `/settings log`\n" + - "A warning log channel can be set with `/settings warnings channel`" + "**General:** These are events like kicks and channel changes etc.\n" + + `> These are standard logs and can be set with ${await getCommandMentionByName("settings/logs/general")}\n` + + "**Warnings:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member.\n" + + `> These may require special action by a moderator. You can set the channel with ${await getCommandMentionByName("settings/logs/warnings")}\n` + // TODO + "**Attachments:** All images sent in the server - Used to keep a record of deleted images\n" + + `> Sent to a separate log channel to avoid spam. This can be set with ${await getCommandMentionByName("settings/logs/attachments")}\n` + + `> ${getEmojiByName("NUCLEUS.PREMIUM")} Please note this feature is only available with ${await getCommandMentionByName("nucleus/premium")}` ) .setEmoji("ICONS.LOGGING") .setStatus("Danger") @@ -76,27 +90,15 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Moderation") .setDescription( "Nucleus has a number of commands that can be used to moderate your server.\n" + - "These commands are all found under `/mod`, and they include:\n" + - `**${getEmojiByName( - "PUNISH.WARN.YELLOW" - )} Warn:** The user is warned (via DM) that they violated server rules.\n` + - `**${getEmojiByName( - "PUNISH.CLEARHISTORY" - )} Clear:** Some messages from a user are deleted in a channel.\n` + - `**${getEmojiByName( - "PUNISH.MUTE.YELLOW" - )} Mute:** The user is unable to send messages or join voice chats.\n` + - `**${getEmojiByName( - "PUNISH.MUTE.GREEN" - )} Unmute:** The user is able to send messages in the server.\n` + - `**${getEmojiByName("PUNISH.KICK.RED")} Kick:** The user is removed from the server.\n` + - `**${getEmojiByName( - "PUNISH.SOFTBAN" - )} Softban:** Kicks the user, deleting their messages from every channel.\n` + - `**${getEmojiByName( - "PUNISH.BAN.RED" - )} Ban:** The user is removed from the server, and they are unable to rejoin.\n` + - `**${getEmojiByName("PUNISH.BAN.GREEN")} Unban:** The user is able to rejoin the server.` + `These commands are all found under ${await getCommandMentionByName(("mod"))}, and they include:\n` + + `${getEmojiByName("PUNISH.WARN.YELLOW")} ${await getCommandMentionByName("mod/warn")}: The user is warned (via DM) that they violated server rules. More options given if DMs are disabled.\n` + + `${getEmojiByName("PUNISH.CLEARHISTORY")} ${await getCommandMentionByName("mod/purge")}: Deletes messages in a channel, giving options to only delete messages by a certain user.\n` + + `${getEmojiByName("PUNISH.MUTE.YELLOW")} ${await getCommandMentionByName("mod/mute")}: Stops users sending messages or joining voice chats.\n` + + `${getEmojiByName("PUNISH.MUTE.GREEN")} ${await getCommandMentionByName("mod/unmute")}: Allows user to send messages and join voice chats.\n` + + `${getEmojiByName("PUNISH.KICK.RED")} ${await getCommandMentionByName("mod/kick")}: Removes a member from the server. They will be able to rejoin.\n` + + `${getEmojiByName("PUNISH.SOFTBAN")} ${await getCommandMentionByName("mod/softban")}: Kicks the user, deleting their messages from every channel in a given time frame.\n` + + `${getEmojiByName("PUNISH.BAN.RED")} ${await getCommandMentionByName("mod/ban")}: Removes the user from the server, deleting messages from every channel and stops them from rejoining.\n` + + `${getEmojiByName("PUNISH.BAN.GREEN")} ${await getCommandMentionByName("mod/unban")}: Allows a member to rejoin the server after being banned.` ) .setEmoji("PUNISH.BAN.RED") .setStatus("Danger") @@ -110,9 +112,9 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Verify") .setDescription( "Nucleus has a verification system that allows users to prove they aren't bots.\n" + - "This is done by running `/verify` which sends a message only the user can see, giving them a link to a CAPTCHA to verify.\n" + - "After the user complete's the CAPTCHA, they are given a role and can use the permissions accordingly.\n" + - "You can set the role given with `/settings verify`" + `This is done by running ${await getCommandMentionByName("verify")} which sends a message only the user can see, giving them a link to a website to verify.\n` + + "After the user complete's the check, they are given a role, which can be set to unlock specific channels.\n" + + `You can set the role given with ${await getCommandMentionByName("settings/verify")}` ) .setEmoji("CONTROL.REDTICK") .setStatus("Danger") @@ -126,8 +128,8 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Content Scanning") .setDescription( "Nucleus has a content scanning system that automatically scans links and images sent by users.\n" + - "Nucleus can detect, delete, and punish users for sending NSFW content, or links to scam or adult sites.\n" + - "You can set the threshold for this in `/settings automation`" // TODO + "The staff team can be notified when an NSFW image is detected, or malicious links are sent.\n" + + `You can check and manage what to moderate in ${await getCommandMentionByName("settings/filters")}` ) .setEmoji("MOD.IMAGES.TOOSMALL") .setStatus("Danger") @@ -140,10 +142,12 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { new EmojiEmbed() .setTitle("Tickets") .setDescription( - "Nucleus has a ticket system that allows users to create tickets and have a support team respond to them.\n" + - "Tickets can be created with `/ticket create` and a channel is created, pinging the user and support role.\n" + - "When the ticket is resolved, anyone can run `/ticket close` (or click the button) to close it.\n" + - "Running `/ticket close` again will delete the ticket." + "Nucleus has a ticket system which allows users to create tickets and talk to the server staff or support team.\n" + + `Tickets can be created by users with ${await getCommandMentionByName("ticket/create")}, or by clicking a button created by moderators.\n` + + `After being created, a new channel or thread is created, and the user and support team are pinged. \n` + + `The category or channel to create threads in can be set with ${await getCommandMentionByName("settings/tickets")}\n` + + `When the ticket is resolved, anyone can run ${await getCommandMentionByName("ticket/close")} (or click the button) to close it.\n` + + `Running ${await getCommandMentionByName("ticket/close")} again will delete the ticket.` ) .setEmoji("GUILD.TICKET.CLOSE") .setStatus("Danger") @@ -156,11 +160,10 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { new EmojiEmbed() .setTitle("Tags") .setDescription( - "Add a tag system to your server with the `/tag` and `/tags` commands.\n" + - "To create a tag, type `/tags create `.\n" + - "Tag names and content can be edited with `/tags edit`.\n" + - "To delete a tag, type `/tags delete `.\n" + - "To view all tags, type `/tags list`.\n" + "Nucleus allows you to create tags, which allow a message to be sent when a specific tag is typed.\n" + + `Tags can be created with ${await getCommandMentionByName("tags/create")}, and can be edited with ${await getCommandMentionByName("tags/edit")}\n` + + `Tags can be deleted with ${await getCommandMentionByName("tags/delete")}, and can be listed with ${await getCommandMentionByName("tags/list")}\n` + + `To use a tag, you can type ${await getCommandMentionByName("tag")}, followed by the tag to send` ) .setEmoji("PUNISH.NICKNAME.RED") .setStatus("Danger") @@ -173,10 +176,10 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { new EmojiEmbed() .setTitle("Premium") .setDescription( - "In the near future, we will be releasing extra premium only features.\n" + - "These features will include:\n\n" + - "**Attachment logs**\n> When a message with attachments is edited or deleted, the logs will also include the images sent.\n" + - "\nPremium is not yet available. Check `/nucleus premium` for updates on features and pricing" + "Nucleus Premium allows you to use extra features in your server, which are useful but not essential.\n" + + "**No currently free commands will become premium features.**\n" + + "Premium features include creating ticket transcripts and attachment logs.\n\n" + + "Premium can be purchased in [our server](https://discord.gg/bPaNnxe) in the subscriptions page" // TODO: add a table graphic ) .setEmoji("NUCLEUS.PREMIUM") .setStatus("Danger") @@ -185,17 +188,6 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setDescription("Premium features") .setPageId(7) ]; - let m: Message; - if (interaction) { - m = (await interaction.reply({ - embeds: LoadingEmbed, - fetchReply: true, - ephemeral: true - })) as Message; - } else { - m = await c.send({ embeds: LoadingEmbed }); - } - let page = 0; const publicFilter = async (component: MessageComponentInteraction) => { return (component.member as Discord.GuildMember).permissions.has("ManageGuild"); diff --git a/src/utils/client.ts b/src/utils/client.ts index a199b5b..2e2baa6 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,3 +1,4 @@ +import { ApplicationCommand, ApplicationCommandResolvable, DataManager } from 'discord.js'; import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits, Collection } from 'discord.js'; import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; @@ -23,6 +24,7 @@ class NucleusClient extends Client { eventScheduler: EventScheduler; performanceTest: PerformanceTest; }; + commandList?: Discord.Collection; preloadPage: Record = {}; // e.g. { channelID: { command: privacy, page: 3}} commands: Record => { const split = name.replaceAll("/", " ").split(" ") const commandName: string = split[0]!; - let commandID: string; const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName; - if (config.enableDevelopment) { - const developmentGuild = client.guilds.cache.get(config.developmentGuildID)!; - await developmentGuild.commands.fetch(); - commandID = developmentGuild.commands.cache.filter(c => filterCommand(c)).first()!.id; - } else { - await client.application?.commands.fetch(); - commandID = client.application?.commands.cache.filter(c => filterCommand(c)).first()!.id!; - } + const command = client.commandList!.filter(c => filterCommand(c)) + if (command.size === 0) return `\`/${name.replaceAll("/", " ")}\``; + const commandID = command.first()!.id; return ``; -} \ No newline at end of file +} From f86ba0999dd65b2603c1ad2795d2b73cfdd0d69c Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 27 Jan 2023 17:10:07 -0500 Subject: [PATCH 14/74] removed discordjs/builders, worked on help --- src/Unfinished/all.ts | 2 +- src/Unfinished/categorizationTest.ts | 2 +- src/actions/createModActionTicket.ts | 6 +- src/actions/tickets/create.ts | 6 +- src/actions/tickets/delete.ts | 6 +- src/commands/help.ts | 162 +++++++++++++----- src/commands/mod/about.ts | 2 +- src/commands/mod/ban.ts | 10 +- src/commands/mod/kick.ts | 20 ++- src/commands/mod/mute.ts | 10 +- src/commands/mod/nick.ts | 10 +- src/commands/mod/purge.ts | 9 +- src/commands/mod/slowmode.ts | 9 +- src/commands/mod/softban.ts | 9 +- src/commands/mod/unban.ts | 9 +- src/commands/mod/unmute.ts | 11 +- src/commands/mod/viewas.ts | 4 +- src/commands/mod/warn.ts | 9 +- src/commands/nucleus/guide.ts | 7 +- src/commands/nucleus/invite.ts | 7 +- src/commands/nucleus/ping.ts | 7 +- src/commands/nucleus/premium.ts | 7 +- src/commands/nucleus/stats.ts | 7 +- src/commands/nucleus/suggest.ts | 7 +- src/commands/privacy.ts | 7 +- src/commands/role/user.ts | 9 +- src/commands/rolemenu.ts | 8 +- src/commands/server/about.ts | 7 +- src/commands/settings/commands.ts | 4 +- src/commands/settings/filters.ts | 4 +- src/commands/settings/logs/attachment.ts | 4 +- src/commands/settings/logs/channel.ts | 4 +- src/commands/settings/logs/events.ts | 4 +- src/commands/settings/logs/staff.ts | 4 +- src/commands/settings/rolemenu.ts | 4 +- src/commands/settings/stats.ts | 4 +- src/commands/settings/tickets.ts | 4 +- src/commands/settings/verify.ts | 4 +- src/commands/settings/welcome.ts | 34 ++-- src/commands/tag.ts | 8 +- src/commands/tags/create.ts | 4 +- src/commands/tags/delete.ts | 4 +- src/commands/tags/edit.ts | 4 +- src/commands/tags/list.ts | 6 +- src/commands/ticket/close.ts | 7 +- src/commands/ticket/create.ts | 6 +- src/commands/user/about.ts | 7 +- src/commands/user/avatar.ts | 6 +- src/commands/user/track.ts | 4 +- src/commands/verify.ts | 7 +- src/config/emojis.json | 5 +- src/index.ts | 6 +- src/premium/attachmentLogs.ts | 6 +- src/reflex/guide.ts | 48 +++--- src/reflex/statsChannelUpdate.ts | 4 +- src/reflex/welcome.ts | 6 +- src/utils/client.ts | 8 +- src/utils/commandRegistration/register.ts | 24 +-- .../slashCommandBuilder.ts | 6 +- src/utils/confirmationMessage.ts | 4 +- src/utils/generateEmojiEmbed.ts | 2 +- src/utils/getCommandDataByName.ts | 27 +++ src/utils/getCommandMentionByName.ts | 15 -- 63 files changed, 343 insertions(+), 324 deletions(-) create mode 100644 src/utils/getCommandDataByName.ts delete mode 100644 src/utils/getCommandMentionByName.ts diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts index d09ca7b..a6379b7 100644 --- a/src/Unfinished/all.ts +++ b/src/Unfinished/all.ts @@ -9,7 +9,7 @@ import Discord, { StringSelectMenuBuilder, APIMessageComponentEmoji } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; import addPlural from "../utils/plurals.js"; diff --git a/src/Unfinished/categorizationTest.ts b/src/Unfinished/categorizationTest.ts index cbd9924..ff2d66b 100644 --- a/src/Unfinished/categorizationTest.ts +++ b/src/Unfinished/categorizationTest.ts @@ -1,6 +1,6 @@ import { LoadingEmbed } from "../utils/defaults.js"; import { CommandInteraction, GuildChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, StringSelectMenuBuilder, APIMessageComponentEmoji } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; +import { SlashCommandBuilder } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import client from "../utils/client.js"; import addPlural from "../utils/plurals.js"; diff --git a/src/actions/createModActionTicket.ts b/src/actions/createModActionTicket.ts index d6e9cd9..24c0057 100644 --- a/src/actions/createModActionTicket.ts +++ b/src/actions/createModActionTicket.ts @@ -1,4 +1,4 @@ -import { getCommandMentionByName } from './../utils/getCommandMentionByName.js'; +import { getCommandMentionByName } from './../utils/getCommandDataByName.js'; import Discord, { ActionRowBuilder, ButtonBuilder, OverwriteType, ChannelType, ButtonStyle } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; @@ -86,7 +86,7 @@ export async function create( `**Support type:** ${customReason ? customReason : "Appeal submission"}\n` + (reason !== null ? `**Reason:**\n> ${reason}\n` : "") + `**Ticket ID:** \`${c.id}\`\n` + - `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.` + `Type ${getCommandMentionByName("ticket/close")} to close this ticket.` ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") @@ -131,7 +131,7 @@ export async function create( `**Support type:** ${customReason ? customReason : "Appeal submission"}\n` + (reason !== null ? `**Reason:**\n> ${reason}\n` : "") + `**Ticket ID:** \`${c.id}\`\n` + - `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.` + `Type ${getCommandMentionByName("ticket/close")} to close this ticket.` ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts index 8a021d4..3e5cacd 100644 --- a/src/actions/tickets/create.ts +++ b/src/actions/tickets/create.ts @@ -3,7 +3,7 @@ import { tickets, toHexArray } from "../../utils/calculate.js"; import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; -import { getCommandMentionByName } from "../../utils/getCommandMentionByName.js"; +import { getCommandMentionByName } from "../../utils/getCommandDataByName.js"; function capitalize(s: string) { s = s.replace(/([A-Z])/g, " $1"); @@ -225,7 +225,7 @@ export default async function (interaction: CommandInteraction | ButtonInteracti chosenType !== null ? emoji + " " + capitalize(chosenType) : "General" }\n` + `**Ticket ID:** \`${c.id}\`\n${content ?? ""}\n` + - `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.` + `Type ${getCommandMentionByName("ticket/close")} to close this ticket.` ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") @@ -289,7 +289,7 @@ export default async function (interaction: CommandInteraction | ButtonInteracti chosenType !== null ? emoji + " " + capitalize(chosenType) : "General" }\n` + `**Ticket ID:** \`${c.id}\`\n${content ?? ""}\n` + - `Type ${await getCommandMentionByName("ticket/close")} to close this ticket.` + `Type ${getCommandMentionByName("ticket/close")} to close this ticket.` ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index 3263580..fbfa221 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -1,4 +1,4 @@ -import { getCommandMentionByName } from '../../utils/getCommandMentionByName.js'; +import { getCommandMentionByName } from '../../utils/getCommandDataByName.js'; import Discord, { ActionRowBuilder, ButtonBuilder, ButtonInteraction, PrivateThreadChannel, TextChannel, ButtonStyle, CategoryChannel } from "discord.js"; import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -84,9 +84,9 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI embeds: [ new EmojiEmbed() .setTitle("Archived Ticket") - .setDescription(`This ticket has been Archived. Type ${await getCommandMentionByName("ticket/close")} to delete it.` + + .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.` + await client.database.premium.hasPremium(interaction.guild.id) ? - `\n\nFor more info on transcripts, check ${await getCommandMentionByName("privacy")}` : + `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}` : "") .setStatus("Warning") .setEmoji("GUILD.TICKET.ARCHIVED") diff --git a/src/commands/help.ts b/src/commands/help.ts index 26d86c5..c1bad9b 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,33 +1,71 @@ -import { ActionRowBuilder, CommandInteraction, StringSelectMenuBuilder, ApplicationCommand, ApplicationCommandOptionType } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; +import { + ActionRowBuilder, + CommandInteraction, + StringSelectMenuBuilder, + ApplicationCommandOptionType, + ApplicationCommandType, + StringSelectMenuOptionBuilder, + SlashCommandBuilder, + StringSelectMenuInteraction, + ComponentType, + APIMessageComponentEmoji, + ApplicationCommandSubGroup, + PermissionsBitField, + Interaction, + ApplicationCommandOption, + ApplicationCommandSubCommand +} from "discord.js"; import client from "../utils/client.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import { LoadingEmbed } from "../utils/defaults.js"; +import { capitalize } from "../utils/generateKeyValueList.js"; +import { getCommandByName, getCommandMentionByName } from "../utils/getCommandDataByName.js"; +import getEmojiByName from "../utils/getEmojiByName.js"; const command = new SlashCommandBuilder() .setName("help") .setDescription("Shows help for commands"); +const styles: Record = { + "help": {emoji: "NUCLEUS.LOGO"}, + "mod": {emoji: "PUNISH.BAN.RED"}, + "nucleus": {emoji: "NUCLEUS.LOGO"}, + "privacy": {emoji: "NUCLEUS.LOGO"}, + "role": {emoji: "GUILD.ROLES.DELETE"}, + "rolemenu": {emoji: "GUILD.ROLES.DELETE"}, + "server": {emoji: "GUILD.RED"}, + "settings": {emoji: "GUILD.SETTINGS.RED"}, + "tag": {emoji: "PUNISH.NICKNAME.RED"}, + "tags": {emoji: "PUNISH.NICKNAME.RED"}, + "ticket": {emoji: "GUILD.TICKET.CLOSE"}, + "user": {emoji: "MEMBER.LEAVE"}, + "verify": {emoji: "CONTROL.BLOCKTICK"} +} + const callback = async (interaction: CommandInteraction): Promise => { - const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true }); + const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); const commands = client.fetchedCommands; - const commandRow = new ActionRowBuilder() - .addComponents( - commands.map((command) => { - return new StringSelectMenuBuilder() - .setCustomId(command.name) - .setPlaceholder("Select a command") - .addOptions({ - label: command.name, - value: command.name - }) - }) - ); - let closed = false; + let currentPath: [string, string, string] = ["","",""] do { - let current: ApplicationCommand | undefined; + const commandRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("commandRow") + .setPlaceholder("Select a command") + .addOptions( + ...commands.filter(command => command.type === ApplicationCommandType.ChatInput).map((command) => { + const builder = new StringSelectMenuOptionBuilder() + .setLabel(capitalize(command.name)) + .setValue(command.name) + .setDescription(command.description) + .setDefault(currentPath[0] === command.name) + if (styles[command.name]) builder.setEmoji(getEmojiByName(styles[command.name]!.emoji, "id") as APIMessageComponentEmoji) + return builder + }) + ) + ); const subcommandGroupRow = new ActionRowBuilder() .addComponents( new StringSelectMenuBuilder() @@ -40,58 +78,96 @@ const callback = async (interaction: CommandInteraction): Promise => { ); const embed = new EmojiEmbed() .setTitle("Help") - .setStatus("Success") - .setEmoji("📖") + .setStatus("Danger") + .setEmoji("NUCLEUS.LOGO") - if(!current) { + if(currentPath[0] === "" || currentPath[0] === "help") { embed.setDescription( - `**${"Help Menu Home"}**\n\n` + - `${"Select a command to get started"}` + `Welcome to Nucleus\n\n` + + `Select a command to get started${(interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``}` // FIXME ) } else { + let currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/')); + let current = commands.find((command) => command.name === currentPath[0])!; + + let optionString = `` + let options: (ApplicationCommandOption & { + nameLocalized?: string; + descriptionLocalized?: string; + })[] = []; + //options + for(const option of options) { + optionString += `> ${option.name} (${option.type})- ${option.description}\n` + } + const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0] + let allowedToRun = true; + if(APICommand?.check) { + APICommand?.check(interaction as Interaction, true) + } embed.setDescription( - `**${current.name}**\n\n` + - `${current.description}` + `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${currentData.mention}\n\n` + + `> ${currentData.description}\n\n` + + (APICommand ? (`${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${allowedToRun ? "" : "don't "}` + + `have permission to use this command\n\n`) : "") + + ((optionString.length > 0) ? "**Options:**\n" + optionString : "") ) const subcommands = current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand); const subcommandGroups = current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup); + if(subcommandGroups.length > 0) { subcommandGroupRow.components[0]! .addOptions( - subcommandGroups.map((option) => { - return { - label: option.name, - value: option.name - } - }) + new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[1] === "none"), + ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name)) ) - } else { - if(subcommands.length > 0) { + if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default)) { + let subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand) || []; subcommandRow.components[0]! .addOptions( - subcommands.map((option) => { - return { - label: option.name, - value: option.name - } - }) + new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"), + ...subsubcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[2] === option.name)) ) } } + if(subcommands.length > 0) { + subcommandGroupRow.components[0]! + .addOptions( + ...subcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name)) + ) + } } + let cmps = [commandRow]; if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow); if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow); await interaction.editReply({ embeds: [embed], components: cmps }); + let i: StringSelectMenuInteraction; + try { + i = await m.awaitMessageComponent({filter: (newInteraction) => interaction.user.id === newInteraction.user.id,time: 300000}) + } catch (e) { + closed = true; + break; + } + await i.deferUpdate(); + let value = i.values[0]!; + switch(i.customId) { + case "commandRow": + currentPath = [value, "", ""]; + break; + case "subcommandGroupRow": + currentPath = [currentPath[0], value , ""]; + break; + case "subcommandRow": + currentPath[2] = value; + break; + } + console.log(currentPath) + } while (!closed); }; -const check = () => { - return true; -}; -export { command }; +export { command as command }; export { callback }; -export { check }; diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts index 4277918..d34b634 100644 --- a/src/commands/mod/about.ts +++ b/src/commands/mod/about.ts @@ -12,8 +12,8 @@ import Discord, { ButtonStyle, TextInputStyle, APIMessageComponentEmoji, + SlashCommandSubcommandBuilder } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import client from "../../utils/client.js"; diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index ebc44ad..362bde8 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -1,5 +1,4 @@ -import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle, SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -166,9 +165,12 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = async (interaction: CommandInteraction) => { +const check = async (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; const member = interaction.member as GuildMember; + // Check if the user has ban_members permission + if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; + if(partial) return true; const me = interaction.guild.members.me!; let apply = interaction.options.getUser("user") as User | GuildMember; const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; @@ -190,8 +192,6 @@ const check = async (interaction: CommandInteraction) => { if (member.id === me.id) return "I cannot ban myself"; // Allow the owner to ban anyone if (member.id === interaction.guild.ownerId) return true; - // Check if the user has ban_members permission - if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; // Check if the user is below on the role list if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow ban diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 380bcc9..d9418e6 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -1,8 +1,7 @@ import { LinkWarningFooter } from '../../utils/defaults.js'; -import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; +import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandSubcommandBuilder } from "discord.js"; // @ts-expect-error import humanizeDuration from "humanize-duration"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import type Discord from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -168,26 +167,29 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; + const member = interaction.member as GuildMember; + // Check if the user has kick_members permission + if (!member.permissions.has("KickMembers")) return "You do not have the *Kick Members* permission"; + if (partial) return true; + const me = interaction.guild.members.me!; const apply = interaction.options.getMember("user") as GuildMember; const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0; + // Check if Nucleus has permission to kick + if (!me.permissions.has("KickMembers")) return "I do not have the *Kick Members* permission"; + // Allow the owner to kick anyone + if (member.id === interaction.guild.ownerId) return true; // Do not allow kicking the owner if (member.id === interaction.guild.ownerId) return "You cannot kick the owner of the server"; // Check if Nucleus can kick the member if (!(mePos > applyPos)) return "I do not have a role higher than that member"; - // Check if Nucleus has permission to kick - if (!me.permissions.has("KickMembers")) return "I do not have the *Kick Members* permission"; // Do not allow kicking Nucleus if (member.id === interaction.guild.members.me!.id) return "I cannot kick myself"; - // Allow the owner to kick anyone - if (member.id === interaction.guild.ownerId) return true; - // Check if the user has kick_members permission - if (!member.permissions.has("KickMembers")) return "You do not have the *Kick Members* permission"; // Check if the user is below on the role list if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow kick diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 0e080c3..ef677df 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -1,6 +1,6 @@ import { LinkWarningFooter, LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -361,9 +361,12 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = async (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; const member = interaction.member as GuildMember; + // Check if the user has moderate_members permission + if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission"; + if (partial) return true; const me = interaction.guild.members.me!; const apply = interaction.options.getMember("user") as GuildMember; const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; @@ -379,9 +382,6 @@ const check = (interaction: CommandInteraction) => { if (member.id === me.id) return "I cannot mute myself"; // Allow the owner to mute anyone if (member.id === interaction.guild.ownerId) return true; - // Check if the user has moderate_members permission - if (!member.permissions.has("ModerateMembers")) - return "You do not have the *Moderate Members* permission"; // Check if the user is below on the role list if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow mute diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index abb695d..2787a51 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -1,6 +1,6 @@ import { LinkWarningFooter } from './../../utils/defaults.js'; import { ActionRowBuilder, ButtonBuilder, CommandInteraction, GuildMember, ButtonStyle, Message } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -189,8 +189,11 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = async (interaction: CommandInteraction, partial: boolean = false) => { const member = interaction.member as GuildMember; + // Check if the user has manage_nicknames permission + if (!member.permissions.has("ManageNicknames")) return "You do not have the *Manage Nicknames* permission"; + if (partial) return true; const me = interaction.guild!.members.me!; const apply = interaction.options.getMember("user") as GuildMember; const memberPos = member.roles.cache.size ? member.roles.highest.position : 0; @@ -205,9 +208,6 @@ const check = (interaction: CommandInteraction) => { if (!me.permissions.has("ManageNicknames")) return "I do not have the *Manage Nicknames* permission"; // Allow the owner to change anyone's nickname if (member.id === interaction.guild.ownerId) return true; - // Check if the user has manage_nicknames permission - if (!member.permissions.has("ManageNicknames")) - return "You do not have the *Manage Nicknames* permission"; // Allow changing your own nickname if (member === apply) return true; // Check if the user is below on the role list diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index cc72efb..89f311f 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -1,6 +1,6 @@ import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from '../../utils/logTranscripts.js'; import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -391,16 +391,17 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return false; const member = interaction.member as GuildMember; + // Check if the user has manage_messages permission + if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission"; + if (partial) return true; const me = interaction.guild.members.me!; // Check if nucleus has the manage_messages permission if (!me.permissions.has("ManageMessages")) return "I do not have the *Manage Messages* permission"; // Allow the owner to purge if (member.id === interaction.guild.ownerId) return true; - // Check if the user has manage_messages permission - if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission"; // Allow purge return true; }; diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts index 9792827..886d4bb 100644 --- a/src/commands/mod/slowmode.ts +++ b/src/commands/mod/slowmode.ts @@ -1,7 +1,7 @@ // @ts-expect-error import humanizeDuration from "humanize-duration"; import type { CommandInteraction, GuildMember, TextChannel } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import keyValueList from "../../utils/generateKeyValueList.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -76,12 +76,13 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { const member = interaction.member as GuildMember; - // Check if Nucleus can set the slowmode - if (!interaction.guild!.members.me!.permissions.has("ManageChannels")) return "I do not have the *Manage Channels* permission"; // Check if the user has manage_channel permission if (!member.permissions.has("ManageChannels")) return "You do not have the *Manage Channels* permission"; + if (partial) return true; + // Check if Nucleus can set the slowmode + if (!interaction.guild!.members.me!.permissions.has("ManageChannels")) return "I do not have the *Manage Channels* permission"; // Allow slowmode return true; }; diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index 2c87420..bd940fa 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -1,5 +1,5 @@ import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, User, ButtonStyle } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -167,9 +167,12 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = async (interaction: CommandInteraction) => { +const check = async (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; const member = interaction.member as GuildMember; + // Check if the user has ban_members permission + if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; + if (partial) return true; const me = interaction.guild.members.me!; let apply = interaction.options.getUser("user") as User | GuildMember; const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; @@ -191,8 +194,6 @@ const check = async (interaction: CommandInteraction) => { if (member.id === me.id) return "I cannot softban myself"; // Allow the owner to ban anyone if (member.id === interaction.guild.ownerId) return true; - // Check if the user has ban_members permission - if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; // Check if the user is below on the role list if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow ban diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts index 37fee99..ac4823e 100644 --- a/src/commands/mod/unban.ts +++ b/src/commands/mod/unban.ts @@ -1,5 +1,5 @@ import type { CommandInteraction, GuildMember, User } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -107,16 +107,17 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; const member = interaction.member as GuildMember; + // Check if the user has ban_members permission + if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; + if (partial) return true; const me = interaction.guild.members.me!; // Check if Nucleus can unban members if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission"; // Allow the owner to unban anyone if (member.id === interaction.guild.ownerId) return true; - // Check if the user has ban_members permission - if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission"; // Allow unban return true; }; diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts index e2585e1..4327019 100644 --- a/src/commands/mod/unmute.ts +++ b/src/commands/mod/unmute.ts @@ -1,5 +1,5 @@ import type { CommandInteraction, GuildMember } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -131,9 +131,13 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; const member = interaction.member as GuildMember; + // Check if the user has moderate_members permission + if (!member.permissions.has("ModerateMembers")) + return "You do not have the *Moderate Members* permission"; + if (partial) return true; const me = interaction.guild.members.me!; const apply = interaction.options.getMember("user") as GuildMember; const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; @@ -147,9 +151,6 @@ const check = (interaction: CommandInteraction) => { if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission"; // Allow the owner to unmute anyone if (member.id === interaction.guild.ownerId) return true; - // Check if the user has moderate_members permission - if (!member.permissions.has("ModerateMembers")) - return "You do not have the *Moderate Members* permission"; // Check if the user is below on the role list if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow unmute diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts index f01834c..ef62816 100644 --- a/src/commands/mod/viewas.ts +++ b/src/commands/mod/viewas.ts @@ -10,7 +10,7 @@ import Discord, { StringSelectMenuBuilder, APIMessageComponentEmoji } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import type { GuildBasedChannel } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; @@ -169,7 +169,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as GuildMember; if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; return true; diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index 8f67477..c6b4a56 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -1,5 +1,5 @@ import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -275,9 +275,12 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { if (!interaction.guild) return; const member = interaction.member as GuildMember; + if (!member.permissions.has("ModerateMembers")) + return "You do not have the *Moderate Members* permission"; + if(partial) return true; const apply = interaction.options.getMember("user") as GuildMember | null; if (apply === null) return "That member is not in the server"; const memberPos = member.roles.cache.size ? member.roles.highest.position : 0; @@ -287,8 +290,6 @@ const check = (interaction: CommandInteraction) => { // Allow the owner to warn anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user has moderate_members permission - if (!member.permissions.has("ModerateMembers")) - return "You do not have the *Moderate Members* permission"; // Check if the user is below on the role list if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; // Allow warn diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts index d3370ba..270ee62 100644 --- a/src/commands/nucleus/guide.ts +++ b/src/commands/nucleus/guide.ts @@ -1,5 +1,5 @@ import type { CommandInteraction } from 'discord.js'; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import guide from "../../reflex/guide.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -9,10 +9,5 @@ const callback = async (interaction: CommandInteraction) => { guide(interaction.guild!, interaction); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts index fd65e51..b89425a 100644 --- a/src/commands/nucleus/invite.ts +++ b/src/commands/nucleus/invite.ts @@ -1,5 +1,5 @@ import { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -29,10 +29,5 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts index 12f1c6b..1107f34 100644 --- a/src/commands/nucleus/ping.ts +++ b/src/commands/nucleus/ping.ts @@ -1,6 +1,6 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -28,10 +28,5 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index e74e23c..1c9db24 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,5 +1,5 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import { LoadingEmbed } from "../../utils/defaults.js"; @@ -118,10 +118,5 @@ const callback = async (interaction: CommandInteraction): Promise => { } while (!closed); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts index 8330fbe..19c0949 100644 --- a/src/commands/nucleus/stats.ts +++ b/src/commands/nucleus/stats.ts @@ -1,5 +1,5 @@ import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -19,10 +19,5 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts index de0e69b..6ba3445 100644 --- a/src/commands/nucleus/suggest.ts +++ b/src/commands/nucleus/suggest.ts @@ -1,7 +1,7 @@ import { LoadingEmbed } from '../../utils/defaults.js'; import { ButtonStyle, CommandInteraction } from "discord.js"; import Discord from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -66,10 +66,5 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index e20bf34..cb6054d 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -1,6 +1,5 @@ import { LoadingEmbed } from "../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; +import Discord, { SlashCommandBuilder, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; import createPageIndicator from "../utils/createPageIndicator.js"; @@ -209,10 +208,6 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; export { command }; export { callback }; -export { check }; diff --git a/src/commands/role/user.ts b/src/commands/role/user.ts index ad29811..4ec7f3e 100644 --- a/src/commands/role/user.ts +++ b/src/commands/role/user.ts @@ -1,5 +1,5 @@ import type { CommandInteraction, GuildMember, Role, User } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -78,8 +78,11 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, partial: boolean = false) => { const member = interaction.member as GuildMember; + // Check if the user has manage_roles permission + if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; + if (partial) return true; if (!interaction.guild) return const me = interaction.guild.members.me!; const apply = interaction.options.getMember("user") as GuildMember | null; @@ -88,8 +91,6 @@ const check = (interaction: CommandInteraction) => { if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission"; // Allow the owner to role anyone if (member.id === interaction.guild.ownerId) return true; - // Check if the user has manage_roles permission - if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; // Allow role return true; }; diff --git a/src/commands/rolemenu.ts b/src/commands/rolemenu.ts index c1ceb2e..2861e05 100644 --- a/src/commands/rolemenu.ts +++ b/src/commands/rolemenu.ts @@ -1,5 +1,4 @@ -import type { CommandInteraction } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, SlashCommandBuilder } from "discord.js"; import { callback as roleMenu } from "../actions/roleMenu.js"; const command = new SlashCommandBuilder() @@ -10,10 +9,5 @@ const callback = async (interaction: CommandInteraction): Promise => { await roleMenu(interaction); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/server/about.ts b/src/commands/server/about.ts index 23a53b7..4c88365 100644 --- a/src/commands/server/about.ts +++ b/src/commands/server/about.ts @@ -1,5 +1,5 @@ import { CommandInteraction, GuildMFALevel } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import generateKeyValueList, { toCapitals } from "../../utils/generateKeyValueList.js"; @@ -74,10 +74,5 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/settings/commands.ts b/src/commands/settings/commands.ts index f3a3538..bbbb24a 100644 --- a/src/commands/settings/commands.ts +++ b/src/commands/settings/commands.ts @@ -2,7 +2,7 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder, Message } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -208,7 +208,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/filters.ts b/src/commands/settings/filters.ts index 2e6f4c5..7636f91 100644 --- a/src/commands/settings/filters.ts +++ b/src/commands/settings/filters.ts @@ -1,6 +1,6 @@ import type Discord from "discord.js"; import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("filter").setDescription("Setting for message filters"); @@ -9,7 +9,7 @@ const callback = async (_interaction: CommandInteraction): Promise => { console.log("Filters"); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageMessages")) return "You must have the *Manage Messages* permission to use this command"; diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index f0ecbe9..6f825bc 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -4,7 +4,7 @@ import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonSty import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -192,7 +192,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts index afd7735..9861d26 100644 --- a/src/commands/settings/logs/channel.ts +++ b/src/commands/settings/logs/channel.ts @@ -4,7 +4,7 @@ import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonSty import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -185,7 +185,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index 1249d2b..7259fa4 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -1,6 +1,6 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder } from "discord.js"; -import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders"; +import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import client from "../../../utils/client.js"; import { toHexArray, toHexInteger } from "../../../utils/calculate.js"; @@ -105,7 +105,7 @@ const callback = async (interaction: CommandInteraction): Promise => { // return; }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts index ba9bbba..3855d34 100644 --- a/src/commands/settings/logs/staff.ts +++ b/src/commands/settings/logs/staff.ts @@ -4,7 +4,7 @@ import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonSty import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -189,7 +189,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index f8fb6f4..2cdd1f8 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -1,6 +1,6 @@ import type Discord from "discord.js"; import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, ModalBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import client from "../../utils/client.js"; @@ -347,7 +347,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } while (!closed) }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageRoles")) return "You must have the *Manage Roles* permission to use this command"; diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index 91da382..f8c7d1e 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,7 +1,7 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; import singleNotify from "../../utils/singleNotify.js"; @@ -390,7 +390,7 @@ const callback = async (interaction: CommandInteraction) => { } while (!closed); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageChannels")) return "You must have the *Manage Channels* permission to use this command"; diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 6c74939..3c5746c 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -17,7 +17,7 @@ import Discord, { ModalSubmitInteraction, APIMessageComponentEmoji } from "discord.js"; -import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders"; +import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js"; import { ChannelType } from "discord-api-types/v9"; import client from "../../utils/client.js"; import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js"; @@ -738,7 +738,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t return data; } -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 23fc99b..3467aee 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -17,7 +17,7 @@ import Discord, { import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; @@ -385,7 +385,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index fcd0f76..b9c1b9f 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -12,7 +12,7 @@ import Discord, { GuildChannel, EmbedBuilder } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -198,7 +198,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }\n` + `**Channel:** ${ config.welcome.channel - ? config.welcome.channel == "dm" + ? config.welcome.channel === "dm" ? "DM" : renderChannel((await interaction.guild!.channels.fetch(config.welcome.channel))!) : "*None set*" @@ -210,25 +210,25 @@ const callback = async (interaction: CommandInteraction): Promise => { components: [ new ActionRowBuilder().addComponents([ new ButtonBuilder() - .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message") + .setLabel(lastClicked === "clear-message" ? "Click again to confirm" : "Clear Message") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) .setCustomId("clear-message") .setDisabled(!config.welcome.message) .setStyle(ButtonStyle.Danger), new ButtonBuilder() - .setLabel(lastClicked == "clear-role" ? "Click again to confirm" : "Clear Role") + .setLabel(lastClicked === "clear-role" ? "Click again to confirm" : "Clear Role") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) .setCustomId("clear-role") .setDisabled(!config.welcome.role) .setStyle(ButtonStyle.Danger), new ButtonBuilder() - .setLabel(lastClicked == "clear-ping" ? "Click again to confirm" : "Clear Ping") + .setLabel(lastClicked === "clear-ping" ? "Click again to confirm" : "Clear Ping") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) .setCustomId("clear-ping") .setDisabled(!config.welcome.ping) .setStyle(ButtonStyle.Danger), new ButtonBuilder() - .setLabel(lastClicked == "clear-channel" ? "Click again to confirm" : "Clear Channel") + .setLabel(lastClicked === "clear-channel" ? "Click again to confirm" : "Clear Channel") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) .setCustomId("clear-channel") .setDisabled(!config.welcome.channel) @@ -236,7 +236,7 @@ const callback = async (interaction: CommandInteraction): Promise => { new ButtonBuilder() .setLabel("Set Channel to DM") .setCustomId("set-channel-dm") - .setDisabled(config.welcome.channel == "dm") + .setDisabled(config.welcome.channel === "dm") .setStyle(ButtonStyle.Secondary) ]) ] @@ -252,8 +252,8 @@ const callback = async (interaction: CommandInteraction): Promise => { continue; } await i.deferUpdate(); - if (i.customId == "clear-message") { - if (lastClicked == "clear-message") { + if (i.customId === "clear-message") { + if (lastClicked === "clear-message") { await client.database.guilds.write(interaction.guild!.id, { "welcome.message": null }); @@ -261,8 +261,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } else { lastClicked = "clear-message"; } - } else if (i.customId == "clear-role") { - if (lastClicked == "clear-role") { + } else if (i.customId === "clear-role") { + if (lastClicked === "clear-role") { await client.database.guilds.write(interaction.guild!.id, { "welcome.role": null }); @@ -270,8 +270,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } else { lastClicked = "clear-role"; } - } else if (i.customId == "clear-ping") { - if (lastClicked == "clear-ping") { + } else if (i.customId === "clear-ping") { + if (lastClicked === "clear-ping") { await client.database.guilds.write(interaction.guild!.id, { "welcome.ping": null }); @@ -279,8 +279,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } else { lastClicked = "clear-ping"; } - } else if (i.customId == "clear-channel") { - if (lastClicked == "clear-channel") { + } else if (i.customId === "clear-channel") { + if (lastClicked === "clear-channel") { await client.database.guilds.write(interaction.guild!.id, { "welcome.channel": null }); @@ -288,7 +288,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } else { lastClicked = "clear-channel"; } - } else if (i.customId == "set-channel-dm") { + } else if (i.customId === "set-channel-dm") { await client.database.guilds.write(interaction.guild!.id, { "welcome.channel": "dm" }); @@ -301,7 +301,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; diff --git a/src/commands/tag.ts b/src/commands/tag.ts index a65947c..6ffecca 100644 --- a/src/commands/tag.ts +++ b/src/commands/tag.ts @@ -1,5 +1,4 @@ -import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; +import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from "discord.js"; import client from "../utils/client.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import { capitalize } from "../utils/generateKeyValueList.js"; @@ -51,10 +50,6 @@ const callback = async (interaction: CommandInteraction): Promise => { return; }; -const check = () => { - return true; -}; - const autocomplete = async (interaction: AutocompleteInteraction): Promise => { if (!interaction.guild) return []; const prompt = interaction.options.getString("tag"); @@ -65,5 +60,4 @@ const autocomplete = async (interaction: AutocompleteInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageMessages")) return "You must have the *Manage Messages* permission to use this command"; diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts index 18143d3..4fdb10f 100644 --- a/src/commands/tags/delete.ts +++ b/src/commands/tags/delete.ts @@ -1,6 +1,6 @@ import type Discord from "discord.js"; import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -68,7 +68,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageMessages")) return "You must have the *Manage Messages* permission to use this command"; diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts index e15f9ac..a6e23ba 100644 --- a/src/commands/tags/edit.ts +++ b/src/commands/tags/edit.ts @@ -1,5 +1,5 @@ import type { CommandInteraction, GuildMember } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -126,7 +126,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = (interaction: CommandInteraction) => { +const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as GuildMember; if (!member.permissions.has("ManageMessages")) return "You must have the *Manage Messages* permission to use this command"; diff --git a/src/commands/tags/list.ts b/src/commands/tags/list.ts index 80ee127..dbb1200 100644 --- a/src/commands/tags/list.ts +++ b/src/commands/tags/list.ts @@ -10,7 +10,7 @@ import Discord, { ButtonComponent, StringSelectMenuBuilder } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; @@ -173,10 +173,6 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; export { command }; export { callback }; -export { check }; diff --git a/src/commands/ticket/close.ts b/src/commands/ticket/close.ts index d2ffaf9..ff9da8b 100644 --- a/src/commands/ticket/close.ts +++ b/src/commands/ticket/close.ts @@ -1,5 +1,5 @@ import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import close from "../../actions/tickets/delete.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("close").setDescription("Closes a ticket"); @@ -8,10 +8,5 @@ const callback = async (interaction: CommandInteraction): Promise => { await close(interaction); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/commands/ticket/create.ts b/src/commands/ticket/create.ts index 91442b5..2f3ddc6 100644 --- a/src/commands/ticket/create.ts +++ b/src/commands/ticket/create.ts @@ -1,5 +1,5 @@ import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import create from "../../actions/tickets/create.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -14,10 +14,6 @@ const callback = async (interaction: CommandInteraction): Promise => { await create(interaction); }; -const check = () => { - return true; -}; export { command }; export { callback }; -export { check }; diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts index b2a3db8..c32bf8a 100644 --- a/src/commands/user/about.ts +++ b/src/commands/user/about.ts @@ -10,7 +10,7 @@ import Discord, { APISelectMenuOption, StringSelectMenuBuilder } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import generateKeyValueList from "../../utils/generateKeyValueList.js"; @@ -286,11 +286,6 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte }); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; export { userAbout }; \ No newline at end of file diff --git a/src/commands/user/avatar.ts b/src/commands/user/avatar.ts index 88b3270..da33f51 100644 --- a/src/commands/user/avatar.ts +++ b/src/commands/user/avatar.ts @@ -1,6 +1,6 @@ import type { CommandInteraction } from "discord.js"; import type Discord from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import generateKeyValueList from "../../utils/generateKeyValueList.js"; import client from "../../utils/client.js"; @@ -35,10 +35,6 @@ const callback = async (interaction: CommandInteraction): Promise => { }); }; -const check = () => { - return true; -}; export { command }; export { callback }; -export { check }; diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts index 7160436..25a784b 100644 --- a/src/commands/user/track.ts +++ b/src/commands/user/track.ts @@ -1,6 +1,6 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, SelectMenuOptionBuilder, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import addPlural from "../../utils/plurals.js"; @@ -207,7 +207,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; -const check = async (interaction: CommandInteraction) => { +const check = async (interaction: CommandInteraction, _partial: boolean = false) => { const tracks = (await client.database.guilds.read(interaction.guild!.id)).tracks; if (tracks.length === 0) return "This server does not have any tracks"; const member = interaction.member as GuildMember; diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 4fafe69..0dd8b24 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -1,5 +1,5 @@ import type { CommandInteraction } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; +import { SlashCommandBuilder } from "discord.js"; import verify from "../reflex/verify.js"; const command = new SlashCommandBuilder().setName("verify").setDescription("Get verified in the server"); @@ -8,10 +8,5 @@ const callback = async (interaction: CommandInteraction): Promise => { verify(interaction); }; -const check = () => { - return true; -}; - export { command }; export { callback }; -export { check }; diff --git a/src/config/emojis.json b/src/config/emojis.json index 05a5e1d..1e62149 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -229,7 +229,10 @@ "EDIT": "729066518549233795", "DELETE": "953035210121953320" }, - "SETTINGS": "752570111063228507", + "SETTINGS": { + "GREEN": "752570111063228507", + "RED": "1068607393728049253" + }, "ICONCHANGE": "729763053612302356", "TICKET": { "OPEN": "853245836331188264", diff --git a/src/index.ts b/src/index.ts index b67da33..306811e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,11 @@ client.on("ready", async () => { console.log(`Logged in as ${client.user!.tag}!`); register(); runServer(client); - client.fetchedCommands = await client.application?.commands.fetch()!; + if (config.enableDevelopment) { + client.fetchedCommands = await client.guilds.cache.get(config.developmentGuildID)?.commands.fetch()!; + } else { + client.fetchedCommands = await client.application?.commands.fetch()!; + } }); process.on("unhandledRejection", (err) => { console.error(err) }); process.on("uncaughtException", (err) => { console.error(err) }); diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 156503a..628c2ec 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -1,4 +1,4 @@ -import { getCommandMentionByName } from './../utils/getCommandMentionByName.js'; +import { getCommandMentionByName } from './../utils/getCommandDataByName.js'; import client from "../utils/client.js"; import keyValueList from "../utils/generateKeyValueList.js"; import singleNotify from "../utils/singleNotify.js"; @@ -38,7 +38,7 @@ export default async function logAttachment(message: Message): Promise { "Nucleus can log server events and keep you informed with what content is being posted to your server.\n" + "We have 2 different types of logs, which each can be configured to send to a channel of your choice:\n" + "**General:** These are events like kicks and channel changes etc.\n" + - `> These are standard logs and can be set with ${await getCommandMentionByName("settings/logs/general")}\n` + + `> These are standard logs and can be set with ${getCommandMentionByName("settings/logs/general")}\n` + "**Warnings:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member.\n" + - `> These may require special action by a moderator. You can set the channel with ${await getCommandMentionByName("settings/logs/warnings")}\n` + // TODO + `> These may require special action by a moderator. You can set the channel with ${getCommandMentionByName("settings/logs/warnings")}\n` + // TODO "**Attachments:** All images sent in the server - Used to keep a record of deleted images\n" + - `> Sent to a separate log channel to avoid spam. This can be set with ${await getCommandMentionByName("settings/logs/attachments")}\n` + - `> ${getEmojiByName("NUCLEUS.PREMIUM")} Please note this feature is only available with ${await getCommandMentionByName("nucleus/premium")}` + `> Sent to a separate log channel to avoid spam. This can be set with ${getCommandMentionByName("settings/logs/attachments")}\n` + + `> ${getEmojiByName("NUCLEUS.PREMIUM")} Please note this feature is only available with ${getCommandMentionByName("nucleus/premium")}` ) .setEmoji("ICONS.LOGGING") .setStatus("Danger") @@ -90,15 +90,15 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Moderation") .setDescription( "Nucleus has a number of commands that can be used to moderate your server.\n" + - `These commands are all found under ${await getCommandMentionByName(("mod"))}, and they include:\n` + - `${getEmojiByName("PUNISH.WARN.YELLOW")} ${await getCommandMentionByName("mod/warn")}: The user is warned (via DM) that they violated server rules. More options given if DMs are disabled.\n` + - `${getEmojiByName("PUNISH.CLEARHISTORY")} ${await getCommandMentionByName("mod/purge")}: Deletes messages in a channel, giving options to only delete messages by a certain user.\n` + - `${getEmojiByName("PUNISH.MUTE.YELLOW")} ${await getCommandMentionByName("mod/mute")}: Stops users sending messages or joining voice chats.\n` + - `${getEmojiByName("PUNISH.MUTE.GREEN")} ${await getCommandMentionByName("mod/unmute")}: Allows user to send messages and join voice chats.\n` + - `${getEmojiByName("PUNISH.KICK.RED")} ${await getCommandMentionByName("mod/kick")}: Removes a member from the server. They will be able to rejoin.\n` + - `${getEmojiByName("PUNISH.SOFTBAN")} ${await getCommandMentionByName("mod/softban")}: Kicks the user, deleting their messages from every channel in a given time frame.\n` + - `${getEmojiByName("PUNISH.BAN.RED")} ${await getCommandMentionByName("mod/ban")}: Removes the user from the server, deleting messages from every channel and stops them from rejoining.\n` + - `${getEmojiByName("PUNISH.BAN.GREEN")} ${await getCommandMentionByName("mod/unban")}: Allows a member to rejoin the server after being banned.` + `These commands are all found under ${getCommandMentionByName(("mod"))}, and they include:\n` + + `${getEmojiByName("PUNISH.WARN.YELLOW")} ${getCommandMentionByName("mod/warn")}: The user is warned (via DM) that they violated server rules. More options given if DMs are disabled.\n` + + `${getEmojiByName("PUNISH.CLEARHISTORY")} ${getCommandMentionByName("mod/purge")}: Deletes messages in a channel, giving options to only delete messages by a certain user.\n` + + `${getEmojiByName("PUNISH.MUTE.YELLOW")} ${getCommandMentionByName("mod/mute")}: Stops users sending messages or joining voice chats.\n` + + `${getEmojiByName("PUNISH.MUTE.GREEN")} ${getCommandMentionByName("mod/unmute")}: Allows user to send messages and join voice chats.\n` + + `${getEmojiByName("PUNISH.KICK.RED")} ${getCommandMentionByName("mod/kick")}: Removes a member from the server. They will be able to rejoin.\n` + + `${getEmojiByName("PUNISH.SOFTBAN")} ${getCommandMentionByName("mod/softban")}: Kicks the user, deleting their messages from every channel in a given time frame.\n` + + `${getEmojiByName("PUNISH.BAN.RED")} ${getCommandMentionByName("mod/ban")}: Removes the user from the server, deleting messages from every channel and stops them from rejoining.\n` + + `${getEmojiByName("PUNISH.BAN.GREEN")} ${getCommandMentionByName("mod/unban")}: Allows a member to rejoin the server after being banned.` ) .setEmoji("PUNISH.BAN.RED") .setStatus("Danger") @@ -112,9 +112,9 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Verify") .setDescription( "Nucleus has a verification system that allows users to prove they aren't bots.\n" + - `This is done by running ${await getCommandMentionByName("verify")} which sends a message only the user can see, giving them a link to a website to verify.\n` + + `This is done by running ${getCommandMentionByName("verify")} which sends a message only the user can see, giving them a link to a website to verify.\n` + "After the user complete's the check, they are given a role, which can be set to unlock specific channels.\n" + - `You can set the role given with ${await getCommandMentionByName("settings/verify")}` + `You can set the role given with ${getCommandMentionByName("settings/verify")}` ) .setEmoji("CONTROL.REDTICK") .setStatus("Danger") @@ -129,7 +129,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setDescription( "Nucleus has a content scanning system that automatically scans links and images sent by users.\n" + "The staff team can be notified when an NSFW image is detected, or malicious links are sent.\n" + - `You can check and manage what to moderate in ${await getCommandMentionByName("settings/filters")}` + `You can check and manage what to moderate in ${getCommandMentionByName("settings/filters")}` ) .setEmoji("MOD.IMAGES.TOOSMALL") .setStatus("Danger") @@ -143,11 +143,11 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Tickets") .setDescription( "Nucleus has a ticket system which allows users to create tickets and talk to the server staff or support team.\n" + - `Tickets can be created by users with ${await getCommandMentionByName("ticket/create")}, or by clicking a button created by moderators.\n` + + `Tickets can be created by users with ${getCommandMentionByName("ticket/create")}, or by clicking a button created by moderators.\n` + `After being created, a new channel or thread is created, and the user and support team are pinged. \n` + - `The category or channel to create threads in can be set with ${await getCommandMentionByName("settings/tickets")}\n` + - `When the ticket is resolved, anyone can run ${await getCommandMentionByName("ticket/close")} (or click the button) to close it.\n` + - `Running ${await getCommandMentionByName("ticket/close")} again will delete the ticket.` + `The category or channel to create threads in can be set with ${getCommandMentionByName("settings/tickets")}\n` + + `When the ticket is resolved, anyone can run ${getCommandMentionByName("ticket/close")} (or click the button) to close it.\n` + + `Running ${getCommandMentionByName("ticket/close")} again will delete the ticket.` ) .setEmoji("GUILD.TICKET.CLOSE") .setStatus("Danger") @@ -161,9 +161,9 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setTitle("Tags") .setDescription( "Nucleus allows you to create tags, which allow a message to be sent when a specific tag is typed.\n" + - `Tags can be created with ${await getCommandMentionByName("tags/create")}, and can be edited with ${await getCommandMentionByName("tags/edit")}\n` + - `Tags can be deleted with ${await getCommandMentionByName("tags/delete")}, and can be listed with ${await getCommandMentionByName("tags/list")}\n` + - `To use a tag, you can type ${await getCommandMentionByName("tag")}, followed by the tag to send` + `Tags can be created with ${getCommandMentionByName("tags/create")}, and can be edited with ${getCommandMentionByName("tags/edit")}\n` + + `Tags can be deleted with ${getCommandMentionByName("tags/delete")}, and can be listed with ${getCommandMentionByName("tags/list")}\n` + + `To use a tag, you can type ${getCommandMentionByName("tag")}, followed by the tag to send` ) .setEmoji("PUNISH.NICKNAME.RED") .setStatus("Danger") diff --git a/src/reflex/statsChannelUpdate.ts b/src/reflex/statsChannelUpdate.ts index db705d9..daa82fd 100644 --- a/src/reflex/statsChannelUpdate.ts +++ b/src/reflex/statsChannelUpdate.ts @@ -1,4 +1,4 @@ -import { getCommandMentionByName } from '../utils/getCommandMentionByName.js'; +import { getCommandMentionByName } from '../utils/getCommandDataByName.js'; import type { Guild, User } from "discord.js"; import type { NucleusClient } from "../utils/client.js"; import type { GuildMember } from "discord.js"; @@ -32,7 +32,7 @@ export async function callback(client: NucleusClient, member?: GuildMember, guil return singleNotify( "statsChannelDeleted", guild!.id, - `One or more of your stats channels have been deleted. You can use ${await getCommandMentionByName("settings/stats")}.\n` + + `One or more of your stats channels have been deleted. You can use ${getCommandMentionByName("settings/stats")}.\n` + `The channels name was: ${deleted!.name}`, "Critical" ); diff --git a/src/reflex/welcome.ts b/src/reflex/welcome.ts index 87bb81a..68f391f 100644 --- a/src/reflex/welcome.ts +++ b/src/reflex/welcome.ts @@ -1,4 +1,4 @@ -import { getCommandMentionByName } from './../utils/getCommandMentionByName.js'; +import { getCommandMentionByName } from './../utils/getCommandDataByName.js'; import type { NucleusClient } from "../utils/client.js"; import convertCurlyBracketString from "../utils/convertCurlyBracketString.js"; import client from "../utils/client.js"; @@ -27,7 +27,7 @@ export async function callback(_client: NucleusClient, member: GuildMember) { }); } else { const channel: GuildChannel | null = await member.guild.channels.fetch(config.welcome.channel) as GuildChannel | null; - if (!channel) return await singleNotify("welcomeChannelDeleted", member.guild.id, `The welcome channel has been deleted or is no longer accessible. Use ${await getCommandMentionByName("settings/welcome")} to set a new one`, "Warning") + if (!channel) return await singleNotify("welcomeChannelDeleted", member.guild.id, `The welcome channel has been deleted or is no longer accessible. Use ${getCommandMentionByName("settings/welcome")} to set a new one`, "Warning") if (!(channel instanceof BaseGuildTextChannel)) return; if (channel.guild.id !== member.guild.id) return; try { @@ -39,7 +39,7 @@ export async function callback(_client: NucleusClient, member: GuildMember) { singleNotify( "welcomeChannelDeleted", member.guild.id, - `The welcome channel has been deleted or is no longer accessible. Use ${await getCommandMentionByName("settings/welcome")} to set a new one`, + `The welcome channel has been deleted or is no longer accessible. Use ${getCommandMentionByName("settings/welcome")} to set a new one`, "Warning" ) } diff --git a/src/utils/client.ts b/src/utils/client.ts index 2e2baa6..2a0702a 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,4 +1,3 @@ -import { ApplicationCommand, ApplicationCommandResolvable, DataManager } from 'discord.js'; import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits, Collection } from 'discord.js'; import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; @@ -24,16 +23,15 @@ class NucleusClient extends Client { eventScheduler: EventScheduler; performanceTest: PerformanceTest; }; - commandList?: Discord.Collection; preloadPage: Record = {}; // e.g. { channelID: { command: privacy, page: 3}} - commands: Record Discord.SlashCommandBuilder) | Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder), callback: (interaction: Interaction) => Promise, - check: (interaction: Interaction) => Promise | boolean, + check: (interaction: Interaction, partial: boolean) => Promise | boolean, autocomplete: (interaction: AutocompleteInteraction) => Promise - }> = {}; + } | undefined,{name: string, description: string}]> = {}; fetchedCommands: Collection = new Collection(); constructor(database: typeof NucleusClient.prototype.database) { super({ intents: [ diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index b4c6e6e..4290064 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -35,9 +35,12 @@ async function registerCommands() { fetched.command.setDMPermission(fetched.allowedInDMs ?? false) fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {}) fetched.command.setDescriptionLocalizations(fetched.descriptionLocalizations ?? {}) - if (fetched.nameLocalizations || fetched.descriptionLocalizations) console.log("AAAAA") + // if (fetched.nameLocalizations || fetched.descriptionLocalizations) commands.push(fetched.command); - client.commands["commands/" + fetched.command.name] = fetched; + client.commands["commands/" + fetched.command.name] = [ + fetched, + {name: fetched.name ?? fetched.command.name, description: fetched.description ?? fetched.command.description} + ]; } i++; console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`) @@ -142,11 +145,11 @@ async function registerCommandHandler() { client.on("interactionCreate", async (interaction: Interaction) => { if (interaction.isUserContextMenuCommand()) {; const commandName = "contextCommands/user/" + interaction.commandName; - execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction) + execute(client.commands[commandName]![0]?.check, client.commands[commandName]![0]?.callback, interaction) return; } else if (interaction.isMessageContextMenuCommand()) { const commandName = "contextCommands/message/" + interaction.commandName; - execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction) + execute(client.commands[commandName]![0]?.check, client.commands[commandName]![0]?.callback, interaction) return; } else if (interaction.isAutocomplete()) { const commandName = interaction.commandName; @@ -155,7 +158,7 @@ async function registerCommandHandler() { const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : ""); - const choices = await client.commands[fullCommandName]?.autocomplete(interaction); + const choices = await client.commands[fullCommandName]![0]?.autocomplete(interaction); const formatted = (choices ?? []).map(choice => { return { name: choice, value: choice } @@ -168,7 +171,7 @@ async function registerCommandHandler() { const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : ""); - const command = client.commands[fullCommandName]; + const command = client.commands[fullCommandName]![0]; const callback = command?.callback; const check = command?.check; execute(check, callback, interaction); @@ -208,20 +211,11 @@ export default async function register() { const guild = await client.guilds.fetch(config.developmentGuildID); console.log(`${colours.purple}Registering commands in ${guild!.name}${colours.none}`) await guild.commands.set(commandList); - client.commandList = guild.commands.cache; } else { console.log(`${colours.blue}Registering commands in production mode${colours.none}`) await client.application?.commands.set(commandList); } } - if (config.enableDevelopment) { - const guild = await client.guilds.fetch(config.developmentGuildID); - await guild.commands.fetch(); - client.commandList = guild.commands.cache; - } else { - await client.application?.commands.fetch(); - client.commandList = client.application?.commands.cache!; - } await registerCommandHandler(); await registerEvents(); console.log(`${colours.green}Registered commands, events and context menus${colours.none}`) diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index b2927d6..a4474ac 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -1,4 +1,4 @@ -import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandGroupBuilder } from "discord.js"; import type { SlashCommandBuilder } from "discord.js"; import config from "../../config/main.json" assert { type: "json" }; import getSubcommandsInFolder from "./getFilesInFolder.js"; @@ -64,6 +64,7 @@ export async function command( bitfield.add(userPermissions) command.setDefaultMemberPermissions(bitfield.bitfield) } + client.commands[commandString!] = [undefined, { name: name, description: description }] for (const subcommand of fetched.subcommands) { let fetchedCommand; @@ -72,11 +73,12 @@ export async function command( } else { fetchedCommand = subcommand.command; } - client.commands[commandString! + "/" + fetchedCommand.name] = subcommand + client.commands[commandString! + "/" + fetchedCommand.name] = [subcommand, { name: fetchedCommand.name, description: fetchedCommand.description }] command.addSubcommand(fetchedCommand); } for (const group of fetched.subcommandGroups) { command.addSubcommandGroup(group.command); + client.commands[commandString! + "/" + group.command.name] = [undefined, { name: group.command.name, description: group.command.description }] }; return command; }; diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 6dc424e..18bcd7b 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -1,4 +1,4 @@ -import { TextInputBuilder } from "@discordjs/builders"; +import { TextInputBuilder } from "discord.js"; import Discord, { CommandInteraction, Interaction, @@ -280,7 +280,7 @@ class confirmationMessage { await this.timeoutError() returnValue.cancelled = true; } - if (success == false) { + if (success === false) { await this.interaction.editReply({ embeds: [new EmojiEmbed() .setTitle(this.title) diff --git a/src/utils/generateEmojiEmbed.ts b/src/utils/generateEmojiEmbed.ts index 2978176..a326fc5 100644 --- a/src/utils/generateEmojiEmbed.ts +++ b/src/utils/generateEmojiEmbed.ts @@ -1,4 +1,4 @@ -import { EmbedBuilder } from "@discordjs/builders"; +import { EmbedBuilder } from "discord.js"; import getEmojiByName from "./getEmojiByName.js"; const colors = { diff --git a/src/utils/getCommandDataByName.ts b/src/utils/getCommandDataByName.ts new file mode 100644 index 0000000..0d4ac47 --- /dev/null +++ b/src/utils/getCommandDataByName.ts @@ -0,0 +1,27 @@ +import type Discord from "discord.js"; +import client from "./client.js"; + + +export const getCommandMentionByName = (name: string): string => { + const split = name.replaceAll("/", " ").split(" ") + const commandName: string = split[0]!; + + const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName; + + const command = client.fetchedCommands.filter(c => filterCommand(c)) + if (command.size === 0) return `\`/${name.replaceAll("/", " ")}\``; + const commandID = command.first()!.id; + return ``; +} + +export const getCommandByName = (name: string): {name: string, description: string, mention: string} => { + + const split = name.replaceAll(" ", "/") + const command = client.commands["commands/" + split]!; + const mention = getCommandMentionByName(name); + return { + name: command[1].name, + description: command[1].description, + mention: mention + } +} \ No newline at end of file diff --git a/src/utils/getCommandMentionByName.ts b/src/utils/getCommandMentionByName.ts deleted file mode 100644 index e5a67c5..0000000 --- a/src/utils/getCommandMentionByName.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type Discord from "discord.js"; -import client from "./client.js"; - - -export const getCommandMentionByName = async (name: string): Promise => { - const split = name.replaceAll("/", " ").split(" ") - const commandName: string = split[0]!; - - const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName; - - const command = client.commandList!.filter(c => filterCommand(c)) - if (command.size === 0) return `\`/${name.replaceAll("/", " ")}\``; - const commandID = command.first()!.id; - return ``; -} From fe0a786c39651114075b3ce800952ea3ffa68c8a Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 27 Jan 2023 20:36:35 -0500 Subject: [PATCH 15/74] Finished Help, fixed command registration --- src/commands/help.ts | 3 +-- src/utils/commandRegistration/register.ts | 6 ++++-- .../commandRegistration/slashCommandBuilder.ts | 16 ++++++++++------ src/utils/confirmationMessage.ts | 2 +- src/utils/getCommandDataByName.ts | 1 + 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index c1bad9b..7214799 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -120,7 +120,7 @@ const callback = async (interaction: CommandInteraction): Promise => { new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[1] === "none"), ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name)) ) - if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default)) { + if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) { let subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand) || []; subcommandRow.components[0]! .addOptions( @@ -163,7 +163,6 @@ const callback = async (interaction: CommandInteraction): Promise => { currentPath[2] = value; break; } - console.log(currentPath) } while (!closed); }; diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 4290064..e146069 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -27,8 +27,8 @@ async function registerCommands() { const last = i === files.length - 1 ? "└" : "├"; if (file.isDirectory()) { console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`) - const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command; - commands.push(fetched); + const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)); + commands.push(fetched.command); } else if (file.name.endsWith(".js")) { console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`) const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`)); @@ -171,6 +171,7 @@ async function registerCommandHandler() { const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : ""); + console.log(fullCommandName, client.commands[fullCommandName]) const command = client.commands[fullCommandName]![0]; const callback = command?.callback; const check = command?.check; @@ -222,4 +223,5 @@ export default async function register() { console.log( (config.enableDevelopment ? `${colours.purple}Bot started in Development mode` : `${colours.blue}Bot started in Production mode`) + colours.none) + console.log(client.commands) }; diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index a4474ac..72f92a2 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -1,4 +1,4 @@ -import type { SlashCommandSubcommandGroupBuilder } from "discord.js"; +import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from "discord.js"; import type { SlashCommandBuilder } from "discord.js"; import config from "../../config/main.json" assert { type: "json" }; import getSubcommandsInFolder from "./getFilesInFolder.js"; @@ -32,7 +32,9 @@ export async function group( if (descriptionLocalizations) { subcommandGroup.setDescriptionLocalizations(descriptionLocalizations) } for (const subcommand of fetched.subcommands) { - subcommandGroup.addSubcommand(subcommand.command); + let processedCommand = subcommand.command(new SlashCommandSubcommandBuilder()); + client.commands["commands/" + path + "/" + processedCommand.name] = [subcommand, { name: processedCommand.name, description: processedCommand.description }] + subcommandGroup.addSubcommand(processedCommand); }; return subcommandGroup; @@ -53,6 +55,8 @@ export async function command( commandString = "commands/" + (commandString ?? path); const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path); console.log(`│ ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`) + console.log({name: name, description: description}) + client.commands[commandString!] = [undefined, { name: name, description: description }] return (command: SlashCommandBuilder) => { command.setName(name) command.setDescription(description) @@ -64,12 +68,11 @@ export async function command( bitfield.add(userPermissions) command.setDefaultMemberPermissions(bitfield.bitfield) } - client.commands[commandString!] = [undefined, { name: name, description: description }] for (const subcommand of fetched.subcommands) { let fetchedCommand; if (subcommand.command instanceof Function) { - fetchedCommand = subcommand.command(new Discord.SlashCommandSubcommandBuilder()); + fetchedCommand = subcommand.command(new SlashCommandSubcommandBuilder()); } else { fetchedCommand = subcommand.command; } @@ -77,8 +80,9 @@ export async function command( command.addSubcommand(fetchedCommand); } for (const group of fetched.subcommandGroups) { - command.addSubcommandGroup(group.command); - client.commands[commandString! + "/" + group.command.name] = [undefined, { name: group.command.name, description: group.command.description }] + let processedCommand = group.command(new SlashCommandSubcommandGroupBuilder()); + client.commands[commandString! + "/" + processedCommand.name] = [undefined, { name: processedCommand.name, description: processedCommand.description }] + command.addSubcommandGroup(processedCommand); }; return command; }; diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 18bcd7b..43a0c8f 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -183,7 +183,7 @@ class confirmationMessage { let component; try { component = await m.awaitMessageComponent({ - filter: (i) => i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id && i.id === m.id, + filter: (i) => i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id, time: 300000 }); } catch (e) { diff --git a/src/utils/getCommandDataByName.ts b/src/utils/getCommandDataByName.ts index 0d4ac47..2d4deb7 100644 --- a/src/utils/getCommandDataByName.ts +++ b/src/utils/getCommandDataByName.ts @@ -18,6 +18,7 @@ export const getCommandByName = (name: string): {name: string, description: stri const split = name.replaceAll(" ", "/") const command = client.commands["commands/" + split]!; + console.log(command) const mention = getCommandMentionByName(name); return { name: command[1].name, From cd187ee3cbda7bdaa8a066435c8fd905abb72f98 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 27 Jan 2023 20:46:12 -0500 Subject: [PATCH 16/74] Finished Help, fixed command registration --- src/commands/help.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/commands/help.ts b/src/commands/help.ts index 7214799..c200e18 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -96,6 +96,21 @@ const callback = async (interaction: CommandInteraction): Promise => { descriptionLocalized?: string; })[] = []; //options + if(subcommandRow.components[0]!.options.find(option => option.data.default)) { + let Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup + let Op2 = Op.options!.find(option => option.name === currentPath[2])! + options = Op2.options || [] + } else if(subcommandGroupRow.components[0]!.options.find(option => option.data.default)) { + let Op = current.options.find(option => option.name === currentPath[1])! + if(Op.type === ApplicationCommandOptionType.SubcommandGroup) { + options = [] + } else { + Op = Op as ApplicationCommandSubCommand + options = Op.options || [] + } + } else { + options = current.options.filter(option => option.type !== ApplicationCommandOptionType.SubcommandGroup && option.type !== ApplicationCommandOptionType.Subcommand) || []; + } for(const option of options) { optionString += `> ${option.name} (${option.type})- ${option.description}\n` } From 8c29523dc0d1f49f5d4333584e880e10ab2ad3c0 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 27 Jan 2023 20:52:24 -0500 Subject: [PATCH 17/74] Finished Help w/ Options this time --- src/commands/help.ts | 6 +++--- src/utils/commandRegistration/register.ts | 4 ++-- src/utils/commandRegistration/slashCommandBuilder.ts | 2 +- src/utils/getCommandDataByName.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index c200e18..1cb1ae2 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -96,11 +96,11 @@ const callback = async (interaction: CommandInteraction): Promise => { descriptionLocalized?: string; })[] = []; //options - if(subcommandRow.components[0]!.options.find(option => option.data.default)) { + if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") { let Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup let Op2 = Op.options!.find(option => option.name === currentPath[2])! options = Op2.options || [] - } else if(subcommandGroupRow.components[0]!.options.find(option => option.data.default)) { + } else if(currentPath[1] !== "" && currentPath[1] !== "none") { let Op = current.options.find(option => option.name === currentPath[1])! if(Op.type === ApplicationCommandOptionType.SubcommandGroup) { options = [] @@ -112,7 +112,7 @@ const callback = async (interaction: CommandInteraction): Promise => { options = current.options.filter(option => option.type !== ApplicationCommandOptionType.SubcommandGroup && option.type !== ApplicationCommandOptionType.Subcommand) || []; } for(const option of options) { - optionString += `> ${option.name} (${option.type})- ${option.description}\n` + optionString += `> ${option.name} (${ApplicationCommandOptionType[option.type]})- ${option.description}\n` } const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0] let allowedToRun = true; diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index e146069..50c8e78 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -171,7 +171,7 @@ async function registerCommandHandler() { const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : ""); - console.log(fullCommandName, client.commands[fullCommandName]) + // console.log(fullCommandName, client.commands[fullCommandName]) const command = client.commands[fullCommandName]![0]; const callback = command?.callback; const check = command?.check; @@ -223,5 +223,5 @@ export default async function register() { console.log( (config.enableDevelopment ? `${colours.purple}Bot started in Development mode` : `${colours.blue}Bot started in Production mode`) + colours.none) - console.log(client.commands) + // console.log(client.commands) }; diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index 72f92a2..b215572 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -55,7 +55,7 @@ export async function command( commandString = "commands/" + (commandString ?? path); const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path); console.log(`│ ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`) - console.log({name: name, description: description}) + // console.log({name: name, description: description}) client.commands[commandString!] = [undefined, { name: name, description: description }] return (command: SlashCommandBuilder) => { command.setName(name) diff --git a/src/utils/getCommandDataByName.ts b/src/utils/getCommandDataByName.ts index 2d4deb7..da3e54b 100644 --- a/src/utils/getCommandDataByName.ts +++ b/src/utils/getCommandDataByName.ts @@ -18,7 +18,7 @@ export const getCommandByName = (name: string): {name: string, description: stri const split = name.replaceAll(" ", "/") const command = client.commands["commands/" + split]!; - console.log(command) + // console.log(command) const mention = getCommandMentionByName(name); return { name: command[1].name, From f4facde097af62abcd0c387d521e8dd697c1ebcc Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 28 Jan 2023 13:42:48 -0500 Subject: [PATCH 18/74] finished settings/rolemenu --- package.json | 4 +- src/commands/settings/rolemenu.ts | 83 ++++++++++++++++++++++++++----- src/commands/settings/stats.ts | 4 +- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 8110b8c..30834cb 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@discordjs/rest": "^0.2.0-canary.0", "@hokify/agenda": "^6.2.12", "@tsconfig/node18-strictest-esm": "^1.0.0", + "@types/lodash": "^4.14.191", "@types/node-cron": "^3.0.1", "@ungap/structured-clone": "^1.0.1", "agenda": "^4.3.0", @@ -17,6 +18,7 @@ "fuse.js": "^6.6.2", "humanize-duration": "^3.27.1", "immutable": "^4.1.0", + "lodash": "^4.17.21", "mongodb": "^4.7.0", "node-cron": "^3.0.0", "node-fetch": "^3.3.0", @@ -24,7 +26,7 @@ "pastebin-api": "^5.1.1", "structured-clone": "^0.2.2", "systeminformation": "^5.17.3", - "typescript": "^5.0.0-dev.20230102", + "typescript": "^4.9.4", "uuid": "^8.3.2" }, "name": "nucleus", diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 2cdd1f8..1591273 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -7,7 +7,9 @@ import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import createPageIndicator from "../../utils/createPageIndicator.js"; import { configToDropdown } from "../../actions/roleMenu.js"; - +import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import lodash from 'lodash'; +const isEqual = lodash.isEqual; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("rolemenu") @@ -25,7 +27,17 @@ interface ObjectSchema { }[]; } -const editNameDescription = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { +const defaultRolePageConfig = { + name: "Role Menu Page", + description: "A new role menu page", + min: 0, + max: 0, + options: [ + {name: "Role 1", description: null, role: "No role set"} + ] +} + +const editNameDescription = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { let {name, description} = data; const modal = new ModalBuilder() @@ -35,14 +47,21 @@ const editNameDescription = async (interaction: StringSelectMenuInteraction | Bu new ActionRowBuilder() .addComponents( new TextInputBuilder() + .setLabel("Name") .setCustomId("name") - .setPlaceholder(name ?? "") + .setPlaceholder("Name here...") // TODO: Make better placeholder .setStyle(TextInputStyle.Short) - .setRequired(true), + .setValue(name ?? "") + .setRequired(true) + ), + new ActionRowBuilder() + .addComponents( new TextInputBuilder() + .setLabel("Description") .setCustomId("description") - .setPlaceholder(description ?? "") + .setPlaceholder("Description here...") // TODO: Make better placeholder .setStyle(TextInputStyle.Short) + .setValue(description ?? "") ) ) const button = new ActionRowBuilder() @@ -54,6 +73,33 @@ const editNameDescription = async (interaction: StringSelectMenuInteraction | Bu .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) ) + await i.showModal(modal) + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Role Menu") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + ], + components: [button] + }); + + let out: Discord.ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) return [name, description]; + if (out.isButton()) return [name, description]; + if(!out.fields) return [name, description]; + name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name; + description = out.fields.fields.find((f) => f.customId === "description")?.value ?? description; return [name, description] } @@ -63,7 +109,7 @@ const ellipsis = (str: string, max: number): string => { return str.slice(0, max - 3) + "..."; } -const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema) => { +const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise => { if (!data) data = { name: "Role Menu Page", description: "A new role menu page", @@ -91,6 +137,11 @@ const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | But ); let back = false + if(data.options.length === 0) { + data.options = [ + {name: "Role 1", description: null, role: "No role set"} + ] + } do { const previewSelect = configToDropdown("Edit Roles", {name: data.name, description: data.description, min: 1, max: 1, options: data.options}); const embed = new EmojiEmbed() @@ -117,23 +168,25 @@ const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | But await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0])); } } else if (i.isButton()) { - await i.deferUpdate(); switch (i.customId) { case "back": + await i.deferUpdate(); back = true; break; case "edit": - let [name, description] = await editNameDescription(interaction, m, data); + let [name, description] = await editNameDescription(i, interaction, m, data); data.name = name ? name : data.name; data.description = description ? description : data.description; break; case "addRole": + await i.deferUpdate(); data.options.push(await createRoleMenuOptionPage(interaction, m)); break; } } } while (!back); + if(isEqual(data, defaultRolePageConfig)) return null; return data; } @@ -184,14 +237,14 @@ const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction data.role = (i as RoleSelectMenuInteraction).values[0]!; } } else if (i.isButton()) { - await i.deferUpdate(); switch (i.customId) { case "back": + await i.deferUpdate(); back = true; break; case "edit": await i.deferUpdate(); - let [name, description] = await editNameDescription(interaction, m, data as {name: string; description: string}); + let [name, description] = await editNameDescription(i, interaction, m, data as {name: string; description: string}); data.name = name ? name : data.name; data.description = description ? description : data.description; break; @@ -315,7 +368,9 @@ const callback = async (interaction: CommandInteraction): Promise => { page++; break; case "add": - currentObject.push(await createRoleMenuPage(i, m)); + let newPage = await createRoleMenuPage(i, m) + if(!newPage) break; + currentObject.push(); page = currentObject.length - 1; break; case "reorder": @@ -330,10 +385,14 @@ const callback = async (interaction: CommandInteraction): Promise => { case "action": switch(i.values[0]) { case "edit": - currentObject[page] = await createRoleMenuPage(i, m, current!); + let edited = await createRoleMenuPage(i, m, current!); + if(!edited) break; + currentObject[page] = edited; modified = true; break; case "delete": + if(page === 0 && currentObject.keys.length - 1 > 0) page++; + else page--; currentObject.splice(page, 1); break; } diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f8c7d1e..dd4027d 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, ModalBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; @@ -18,7 +18,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => const showModal = async (interaction: MessageComponentInteraction, current: { enabled: boolean; name: string; }) => { await interaction.showModal( - new Discord.ModalBuilder() + new ModalBuilder() .setCustomId("modal") .setTitle(`Stats channel name`) .addComponents( From a112f61ac661f023375305e753fe11d5b4f00100 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 28 Jan 2023 18:06:45 -0500 Subject: [PATCH 19/74] updated settings commands Co-authored-by: PineappleFan Co-authored-by: Grey, Skyler --- package.json | 4 +- src/commands/help.ts | 4 +- src/commands/mod/_meta.ts | 2 +- src/commands/mod/about.ts | 8 ++-- src/commands/mod/ban.ts | 4 ++ src/commands/mod/kick.ts | 4 ++ src/commands/mod/mute.ts | 4 ++ src/commands/mod/nick.ts | 4 ++ src/commands/mod/purge.ts | 4 ++ src/commands/mod/slowmode.ts | 4 ++ src/commands/settings/filters/_meta.ts | 8 ++++ src/commands/settings/rolemenu.ts | 59 ++++++++++++++++++++++++++ src/commands/settings/tracks.ts | 26 ++++++++++++ src/reflex/scanners.ts | 2 +- 14 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 src/commands/settings/filters/_meta.ts create mode 100644 src/commands/settings/tracks.ts diff --git a/package.json b/package.json index 30834cb..888ceaf 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "@discordjs/rest": "^0.2.0-canary.0", "@hokify/agenda": "^6.2.12", "@tsconfig/node18-strictest-esm": "^1.0.0", - "@types/lodash": "^4.14.191", "@types/node-cron": "^3.0.1", "@ungap/structured-clone": "^1.0.1", "agenda": "^4.3.0", @@ -11,7 +10,7 @@ "body-parser": "^1.20.0", "chalk": "^5.0.0", "deno": "^0.1.1", - "discord.js": "14.7.1", + "discord.js": "^14.7.1", "eslint": "^8.21.0", "express": "^4.18.1", "form-data": "^4.0.0", @@ -61,6 +60,7 @@ "private": false, "type": "module", "devDependencies": { + "@types/lodash": "^4.14.191", "@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/parser": "^5.32.0", "eslint-config-prettier": "^8.5.0", diff --git a/src/commands/help.ts b/src/commands/help.ts index 1cb1ae2..a040358 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -39,7 +39,7 @@ const styles: Record = { "tags": {emoji: "PUNISH.NICKNAME.RED"}, "ticket": {emoji: "GUILD.TICKET.CLOSE"}, "user": {emoji: "MEMBER.LEAVE"}, - "verify": {emoji: "CONTROL.BLOCKTICK"} + "verify": {emoji: "CONTROL.REDTICK"} } const callback = async (interaction: CommandInteraction): Promise => { @@ -47,7 +47,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const commands = client.fetchedCommands; let closed = false; - let currentPath: [string, string, string] = ["","",""] + let currentPath: [string, string, string] = ["", "", ""] do { const commandRow = new ActionRowBuilder() .addComponents( diff --git a/src/commands/mod/_meta.ts b/src/commands/mod/_meta.ts index af8006c..c5fcca5 100644 --- a/src/commands/mod/_meta.ts +++ b/src/commands/mod/_meta.ts @@ -5,4 +5,4 @@ const description = "Perform moderator actions"; const subcommand = await command(name, description, `mod`); -export { name, description, subcommand as command }; +export { name, description, subcommand as command }; \ No newline at end of file diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts index d34b634..ab3ca49 100644 --- a/src/commands/mod/about.ts +++ b/src/commands/mod/about.ts @@ -435,6 +435,8 @@ const check = (interaction: CommandInteraction) => { return true; }; -export { command }; -export { callback }; -export { check }; +export { command, callback, check }; +export const metadata = { + longDescription: "Shows the moderation history (all previous bans, kicks, warns etc.), and moderator notes for a user.", + premiumOnly: true, +} diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 362bde8..7e67222 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -199,3 +199,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) }; export { command, callback, check }; +export const metadata = { + longDescription: "Removes a member from the server - this will prevent them from rejoining until they are unbanned, and will delete a specified number of days of messages from them.", + premiumOnly: true, +} diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index d9418e6..6743ebf 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -197,3 +197,7 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => { }; export { command, callback, check }; +export const metadata = { + longDescription: "Removes a member from the server. They will be able to rejoin if they have an invite link.", + premiumOnly: true, +} diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index ef677df..b7c1405 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -389,3 +389,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) }; export { command, callback, check }; +export const metadata = { + longDescription: "Stops a member from being able to send messages or join voice channels for a specified amount of time.", + premiumOnly: true, +} diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 2787a51..9d5aa3a 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -217,3 +217,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) }; export { command, callback, check }; +export const metadata = { + longDescription: "Changes the nickname of a member. This is the name that shows in the member list and on messages.", + premiumOnly: true, +} diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 89f311f..6673c97 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -407,3 +407,7 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => { }; export { command, callback, check }; +export const metadata = { + longDescription: "Deletes a specified amount of messages from a channel, optionally from a specific user. Without an amount, you can repeatedly choose a number of messages to delete.", + premiumOnly: true, +} diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts index 886d4bb..8d5b709 100644 --- a/src/commands/mod/slowmode.ts +++ b/src/commands/mod/slowmode.ts @@ -88,3 +88,7 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => { }; export { command, callback, check }; +export const metadata = { + longDescription: "Stops members from being able to send messages without waiting a certain amount of time between messages.", + premiumOnly: true, +} diff --git a/src/commands/settings/filters/_meta.ts b/src/commands/settings/filters/_meta.ts new file mode 100644 index 0000000..d2aff53 --- /dev/null +++ b/src/commands/settings/filters/_meta.ts @@ -0,0 +1,8 @@ +import { group } from "../../../utils/commandRegistration/slashCommandBuilder.js"; + +const name = "filters"; +const description = "Settings for filters"; + +const subcommand = await group(name, description, `settings/filters`) + +export { name, description, subcommand as command}; diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 1591273..8796892 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -9,7 +9,9 @@ import createPageIndicator from "../../utils/createPageIndicator.js"; import { configToDropdown } from "../../actions/roleMenu.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; import lodash from 'lodash'; + const isEqual = lodash.isEqual; + const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("rolemenu") @@ -37,6 +39,60 @@ const defaultRolePageConfig = { ] } +const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, currentObj: ObjectSchema[]) => { + let reorderRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("reorder") + .setPlaceholder("Select a page to move...") + .setMinValues(1) + .addOptions( + currentObj.map((o, i) => new StringSelectMenuOptionBuilder() + .setLabel(o.name) + .setValue(i.toString()) + ) + ) + ); + let buttonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Role Menu") + .setDescription("Select pages in the order you want them to appear.") + .setStatus("Success") + ], + components: [reorderRow, buttonRow] + }); + let out: StringSelectMenuInteraction | ButtonInteraction | null; + try { + out = await m.awaitMessageComponent({ + filter: (i) => i.channel!.id === interaction.channel!.id, + time: 300000 + }) as StringSelectMenuInteraction | ButtonInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) return; + if (out.isButton()) return; + if(!out.values) return; + const values = out.values; + + const newOrder: ObjectSchema[] = currentObj.map((_, i) => { + const index = values.findIndex(v => v === i.toString()); + return currentObj[index]; + }) as ObjectSchema[]; + + return newOrder; +} + const editNameDescription = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { let {name, description} = data; @@ -374,6 +430,9 @@ const callback = async (interaction: CommandInteraction): Promise => { page = currentObject.length - 1; break; case "reorder": + let reordered = await reorderRoleMenuPages(interaction, m, currentObject); + if(!reordered) break; + currentObject = reordered; break; case "save": client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject}); diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts new file mode 100644 index 0000000..0cad55c --- /dev/null +++ b/src/commands/settings/tracks.ts @@ -0,0 +1,26 @@ +import type { CommandInteraction, GuildMember, SlashCommandSubcommandBuilder } from "discord.js"; +import client from "../../utils/client.js"; + + +const command = (builder: SlashCommandSubcommandBuilder) => + builder + .setName("tracks") + .setDescription("Manage the tracks for the server") + + +const callback = async (interaction: CommandInteraction) => { + + + +} + +const check = (interaction: CommandInteraction, _partial: boolean = false) => { + const member = interaction.member as GuildMember; + if (!member.permissions.has("ManageRoles")) + return "You must have the *Manage Server* permission to use this command"; + return true; +}; + +export { command }; +export { callback }; +export { check }; diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts index 9761e4b..f69156a 100644 --- a/src/reflex/scanners.ts +++ b/src/reflex/scanners.ts @@ -80,7 +80,7 @@ export async function testLink(link: string): Promise<{ safe: boolean; tags: str } export async function saveAttachment(link: string): Promise { - const image = (await (await fetch(link)).buffer()).toString("base64"); + const image = (await fetch(link)).arrayBuffer().toString(); const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!); writeFileSync(fileName, image, "base64"); return fileName; From 14d11f6c24ba8d0529c415fda5e16b6c8a722c61 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 28 Jan 2023 20:07:50 -0500 Subject: [PATCH 20/74] rewrote getEmojiByName slightly --- src/utils/getEmojiByName.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts index 3fa2b53..9df17a4 100644 --- a/src/utils/getEmojiByName.ts +++ b/src/utils/getEmojiByName.ts @@ -1,5 +1,7 @@ import emojis from "../config/emojis.json" assert { type: "json" }; +import lodash from 'lodash'; +const isArray = lodash.isArray; interface EmojisIndex { [key: string]: string | EmojisIndex | EmojisIndex[]; } @@ -12,7 +14,7 @@ function getEmojiByName(name: string | null, format?: string): string { if (typeof id === "string" || id === undefined) { throw new Error(`Emoji ${name} not found`); } - if (Array.isArray(id)) { + if (isArray(id)) { id = id[parseInt(part)]; } else { id = id[part]; @@ -21,6 +23,10 @@ function getEmojiByName(name: string | null, format?: string): string { if (typeof id !== "string" && id !== undefined) { throw new Error(`Emoji ${name} not found`); } + return getEmojiFromId(id, format); +} + +function getEmojiFromId(id: string | undefined, format?: string): string { if (format === "id") { if (id === undefined) return "0"; return id.toString(); From b5e9d55b0b543110d7c93612c9292fed3384ce54 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sun, 29 Jan 2023 15:43:26 -0500 Subject: [PATCH 21/74] worked on scanners, database, tracks, some moving around and cleaning up files. --- TODO | 3 +- TODO.json | 3 +- src/commands/settings/rolemenu.ts | 24 +- src/commands/settings/tracks.ts | 395 ++++++++++++++++++++++++- src/commands/user/track.ts | 57 ++-- src/config/emojis.json | 2 +- src/reflex/scanners.ts | 32 +- src/utils/client.ts | 6 +- src/utils/createPageIndicator.ts | 19 ++ src/utils/database.ts | 27 ++ src/utils/ellipsis.ts | 4 + src/utils/performanceTesting/record.ts | 2 +- tsconfig.json | 2 +- 13 files changed, 503 insertions(+), 73 deletions(-) create mode 100644 src/utils/ellipsis.ts diff --git a/TODO b/TODO index 2ab95dc..4af4c22 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ ? Role all Server rules -verificationRequired on welcome -// TODO !IMPORTANT! URL + image hash + file hash database +verificationRequired on welcome \ No newline at end of file diff --git a/TODO.json b/TODO.json index 8b211ef..4637953 100644 --- a/TODO.json +++ b/TODO.json @@ -21,6 +21,5 @@ "everyone": true, "roles": true } - }, - "tracks": [] + } } diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 8796892..9528183 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -8,6 +8,7 @@ import getEmojiByName from "../../utils/getEmojiByName.js"; import createPageIndicator from "../../utils/createPageIndicator.js"; import { configToDropdown } from "../../actions/roleMenu.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import ellipsis from "../../utils/ellipsis.js"; import lodash from 'lodash'; const isEqual = lodash.isEqual; @@ -44,8 +45,9 @@ const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, .addComponents( new StringSelectMenuBuilder() .setCustomId("reorder") - .setPlaceholder("Select a page to move...") - .setMinValues(1) + .setPlaceholder("Select all pages in the order you want them to appear.") + .setMinValues(currentObj.length) + .setMaxValues(currentObj.length) .addOptions( currentObj.map((o, i) => new StringSelectMenuOptionBuilder() .setLabel(o.name) @@ -81,6 +83,7 @@ const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, out = null; } if(!out) return; + out.deferUpdate(); if (out.isButton()) return; if(!out.values) return; const values = out.values; @@ -160,12 +163,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele } -const ellipsis = (str: string, max: number): string => { - if (str.length <= max) return str; - return str.slice(0, max - 3) + "..."; -} - -const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise => { +const editRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise => { if (!data) data = { name: "Role Menu Page", description: "A new role menu page", @@ -321,7 +319,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let modified = false; do { const embed = new EmojiEmbed() - .setTitle("Role Menu Settings") + .setTitle("Role Menu") .setEmoji("GUILD.GREEN") .setStatus("Success"); const noRoleMenus = currentObject.length === 0; @@ -377,7 +375,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDisabled(!modified), ); if(noRoleMenus) { - embed.setDescription("No role menu page have been set up yet. Use the button below to add one.\n\n" + + embed.setDescription("No role menu pages have been set up yet. Use the button below to add one.\n\n" + createPageIndicator(1, 1, undefined, true) ); pageSelect.setDisabled(true); @@ -390,7 +388,7 @@ const callback = async (interaction: CommandInteraction): Promise => { page = Math.min(page, Object.keys(currentObject).length - 1); current = currentObject[page]!; embed.setDescription(`**Currently Editing:** ${current.name}\n\n` + - `**Description:** \`${current.description}\`\n` + + `**Description:**\n> ${current.description}\n` + `\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}` ); @@ -424,7 +422,7 @@ const callback = async (interaction: CommandInteraction): Promise => { page++; break; case "add": - let newPage = await createRoleMenuPage(i, m) + let newPage = await editRoleMenuPage(i, m) if(!newPage) break; currentObject.push(); page = currentObject.length - 1; @@ -444,7 +442,7 @@ const callback = async (interaction: CommandInteraction): Promise => { case "action": switch(i.values[0]) { case "edit": - let edited = await createRoleMenuPage(i, m, current!); + let edited = await editRoleMenuPage(i, m, current!); if(!edited) break; currentObject[page] = edited; modified = true; diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 0cad55c..782f52f 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -1,16 +1,405 @@ -import type { CommandInteraction, GuildMember, SlashCommandSubcommandBuilder } from "discord.js"; +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import client from "../../utils/client.js"; - +import createPageIndicator, { createVerticalTrack } from "../../utils/createPageIndicator.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import ellipsis from "../../utils/ellipsis.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("tracks") .setDescription("Manage the tracks for the server") +interface ObjectSchema { + name: string; + retainPrevious: boolean; + nullable: boolean; + track: string[]; + manageableBy: string[]; +} + +const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, current?: string) => { + + let name = current ?? ""; + const modal = new ModalBuilder() + .setTitle("Edit Name and Description") + .setCustomId("editNameDescription") + .addComponents( + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setLabel("Name") + .setCustomId("name") + .setPlaceholder("Name here...") // TODO: Make better placeholder + .setStyle(TextInputStyle.Short) + .setValue(name ?? "") + .setRequired(true) + ) + ) + const button = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + + await i.showModal(modal) + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Tracks") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + ], + components: [button] + }); + + let out: ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as ModalSubmitInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) return name; + if (out.isButton()) return name; + if(!out.fields) return name; + name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name; + return name + +} + +const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: Collection, currentObj: string[]) => { + let reorderRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("reorder") + .setPlaceholder("Select all roles in the order you want users to gain them (Lowest to highest rank).") + .setMinValues(currentObj.length) + .setMaxValues(currentObj.length) + .addOptions( + currentObj.map((o, i) => new StringSelectMenuOptionBuilder() + .setLabel(roles.get(o)!.name) + .setValue(i.toString()) + ) + ) + ); + let buttonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Tracks") + .setDescription("Select all roles in the order you want users to gain them (Lowest to highest rank).") + .setStatus("Success") + ], + components: [reorderRow, buttonRow] + }); + let out: StringSelectMenuInteraction | ButtonInteraction | null; + try { + out = await m.awaitMessageComponent({ + filter: (i) => i.channel!.id === interaction.channel!.id, + time: 300000 + }) as StringSelectMenuInteraction | ButtonInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) return; + out.deferUpdate(); + if (out.isButton()) return; + if(!out.values) return; + const values = out.values; + + const newOrder: string[] = currentObj.map((_, i) => { + const index = values.findIndex(v => v === i.toString()); + return currentObj[index]; + }) as string[]; + + return newOrder; +} + +const editTrack = async (interaction: ButtonInteraction | StringSelectMenuInteraction, message: Message, roles: Collection, current?: ObjectSchema) => { + if(!current) { + current = { + name: "", + retainPrevious: false, + nullable: false, + track: [], + manageableBy: [] + } + } + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit Name") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("reorder") + .setLabel("Reorder") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji), + ); + const roleSelect = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("addRole") + .setPlaceholder("Select a role to add") + ); + let closed = false; + do { + const editableRoles: string[] = current.track.map((r) => { + if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position)) return r; + }).filter(v => v !== undefined) as string[]; + const selectMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("removeRole") + .setPlaceholder("Select a role to remove") + .addOptions( + editableRoles.map((r, i) => { + return new StringSelectMenuOptionBuilder() + .setLabel(r) + .setValue(i.toString())} + ) + ) + ); + let allowed: boolean[] = []; + for (const role of current.track) { + const disabled: boolean = + roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position; + allowed.push(disabled) + } + + const embed = new EmojiEmbed() + .setTitle("Tracks") + .setDescription( + `**Currently Editing:** ${current.name}\n\n` + + `${getEmojiByName} Members ${current.nullable ? "don't " : ""}need a role in this track` + + `${getEmojiByName} Members ${current.retainPrevious ? "don't " : ""}keep all roles below their current highest` + + createVerticalTrack(current.track, new Array(current.track.length).fill(false), allowed) + ) + + interaction.editReply({embeds: [embed], components: [buttons, roleSelect, selectMenu]}); + + let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null; + + try { + out = await message.awaitMessageComponent({ + filter: (i) => i.channel!.id === interaction.channel!.id, + time: 300000 + }) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + + if(!out) return; + if (out.isButton()) { + out.deferUpdate(); + switch(out.customId) { + case "back": + closed = true; + break; + case "edit": + current.name = (await editName(out, interaction, message, current.name))!; + break; + case "reorder": + current.track = (await reorderTracks(out, message, roles, current.track))!; + } + } else if (out.isStringSelectMenu()) { + out.deferUpdate(); + switch(out.customId) { + case "removeRole": + const index = current.track.findIndex(v => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]); + current.track.splice(index, 1); + break; + } + } else { + switch(out.customId) { + case "addRole": + const role = out.values![0]!; + if(!current.track.includes(role)) { + current.track.push(role); + } + out.reply({content: "That role is already on this track", ephemeral: true}) + break; + } + } + + } while(!closed); + return current; +} const callback = async (interaction: CommandInteraction) => { - + const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true}) + const config = await client.database.guilds.read(interaction.guild!.id); + const tracks: ObjectSchema[] = config.tracks; + const roles = await interaction.guild!.roles.fetch(); + const memberRoles = interaction.member!.roles; + const member = interaction.member as GuildMember; + + let page = 0; + let closed = false; + let modified = false; + + do { + const embed = new EmojiEmbed() + .setTitle("Track Settings") + .setEmoji("TRACKS.ICON") + .setStatus("Success"); + const noTracks = config.tracks.length === 0; + let current: ObjectSchema; + + const pageSelect = new StringSelectMenuBuilder() + .setCustomId("page") + .setPlaceholder("Select a track to manage"); + const actionSelect = new StringSelectMenuBuilder() + .setCustomId("action") + .setPlaceholder("Perform an action") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Edit") + .setDescription("Edit this track") + .setValue("edit") + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new StringSelectMenuOptionBuilder() + .setLabel("Delete") + .setDescription("Delete this track") + .setValue("delete") + .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) + ); + const buttonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + .setDisabled(page === 0), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Primary) + .setDisabled(page === Object.keys(tracks).length - 1), + new ButtonBuilder() + .setCustomId("add") + .setLabel("New Track") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(tracks).length >= 24), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(!modified), + ); + if(noTracks) { + embed.setDescription("No tracks have been set up yet. Use the button below to add one.\n\n" + + createPageIndicator(1, 1, undefined, true) + ); + pageSelect.setDisabled(true); + actionSelect.setDisabled(true); + pageSelect.addOptions(new StringSelectMenuOptionBuilder() + .setLabel("No tracks") + .setValue("none") + ); + } else { + page = Math.min(page, Object.keys(tracks).length - 1); + current = tracks[page]!; + embed.setDescription(`**Currently Editing:** ${current.name}\n\n` + + `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` + + `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` + + createVerticalTrack(current.track, new Array(current.track.length).fill(false)) + + `\n${createPageIndicator(config.tracks.length, page)}` + ); + + pageSelect.addOptions( + tracks.map((key: ObjectSchema, index) => { + return new StringSelectMenuOptionBuilder() + .setLabel(ellipsis(key.name, 50)) + .setDescription(ellipsis(roles.get(key.track[0]!)?.name!, 50)) + .setValue(index.toString()); + }) + ); + + } + + await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder().addComponents(actionSelect), new ActionRowBuilder().addComponents(pageSelect), buttonRow]}); + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + + await i.deferUpdate(); + if (i.isButton()) { + switch (i.customId) { + case "back": + page--; + break; + case "next": + page++; + break; + case "add": + let newPage = await editTrack(i, m, roles) + if(!newPage) break; + tracks.push(); + page = tracks.length - 1; + break; + case "save": + // client.database.guilds.write(interaction.guild!.id, {"roleMenu.options": tracks}); // TODO + modified = false; + break; + } + } else if (i.isStringSelectMenu()) { + switch (i.customId) { + case "action": + switch(i.values[0]) { + case "edit": + let edited = await editTrack(i, m, roles, current!); + if(!edited) break; + tracks[page] = edited; + modified = true; + break; + case "delete": + if(page === 0 && tracks.keys.length - 1 > 0) page++; + else page--; + tracks.splice(page, 1); + break; + } + break; + case "page": + page = parseInt(i.values[0]!); + break; + } + } + + } while (!closed) } diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts index 25a784b..c7f441f 100644 --- a/src/commands/user/track.ts +++ b/src/commands/user/track.ts @@ -1,10 +1,11 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, SelectMenuOptionBuilder, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction } from "discord.js"; +import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import addPlural from "../../utils/plurals.js"; import client from "../../utils/client.js"; +import { createVerticalTrack } from "../../utils/createPageIndicator.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder @@ -12,17 +13,8 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setDescription("Moves a user along a role track") .addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true)); -const generateFromTrack = (position: number, active: string | boolean, size: number, disabled: string | boolean) => { - active = active ? "ACTIVE" : "INACTIVE"; - disabled = disabled ? "GREY." : ""; - if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active; - if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active; - if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active; - return "TRACKS.VERTICAL.MIDDLE." + disabled + active; -}; - const callback = async (interaction: CommandInteraction): Promise => { - const { renderUser } = client.logger; + const { renderUser, renderRole} = client.logger; const member = interaction.options.getMember("user") as GuildMember; const guild = interaction.guild; if (!guild) return; @@ -44,10 +36,10 @@ const callback = async (interaction: CommandInteraction): Promise => { const dropdown = new Discord.StringSelectMenuBuilder() .addOptions( config.tracks.map((option, index) => { - const hasRoleInTrack = option.track.some((element: string) => { + const hasRoleInTrack: boolean = option.track.some((element: string) => { return memberRoles.cache.has(element); }); - return new SelectMenuOptionBuilder({ + return new StringSelectMenuOptionBuilder({ default: index === track, label: option.name, value: index.toString(), @@ -68,33 +60,23 @@ const callback = async (interaction: CommandInteraction): Promise => { (data.retainPrevious ? "When promoted, the user keeps previous roles" : "Members will lose their current role when promoted") + "\n"; - generated += - "\n" + - data.track - .map((role, index) => { - const allow: boolean = - roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position && - !managed; - allowed.push(!allow); - return ( - getEmojiByName( - generateFromTrack(index, memberRoles.cache.has(role), data.track.length, allow) - ) + - " " + - roles.get(role)!.name + - " [<@&" + - roles.get(role)!.id + - ">]" - ); - }) - .join("\n"); + for (const role of data.track) { + const disabled: boolean = + roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position && !managed; + allowed.push(!disabled) + } + generated += "\n" + createVerticalTrack( + data.track.map((role) => renderRole(roles.get(role)!)), + data.track.map((role) => memberRoles.cache.has(role)), + allowed.map((allow) => !allow) + ); const selected = []; for (const position of data.track) { if (memberRoles.cache.has(position)) selected.push(position); } const conflict = data.retainPrevious ? false : selected.length > 1; let conflictDropdown: StringSelectMenuBuilder[] = []; - const conflictDropdownOptions: SelectMenuOptionBuilder[] = []; + const conflictDropdownOptions: StringSelectMenuOptionBuilder[] = []; let currentRoleIndex: number = -1; if (conflict) { generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${ @@ -106,10 +88,9 @@ const callback = async (interaction: CommandInteraction): Promise => { "In order to promote or demote this user, you must select which role the member should keep."; selected.forEach((role) => { conflictDropdownOptions.push( - new SelectMenuOptionBuilder({ - label: roles.get(role)!.name, - value: roles.get(role)!.id - }) + new StringSelectMenuOptionBuilder() + .setLabel(roles.get(role)!.name) + .setValue(roles.get(role)!.id) ); }); conflictDropdown = [ diff --git a/src/config/emojis.json b/src/config/emojis.json index 1e62149..abeb52a 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -25,7 +25,7 @@ "ATTACHMENT": "997570687193587812", "LOGGING": "999613304446144562", "SAVE": "1065722246322200586", - "SHUFFLE": "1067913930304921690", + "SHUFFLE": "1069323453909454890", "NOTIFY": { "ON": "1000726394579464232", "OFF": "1000726363495477368" diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts index f69156a..c8d59f0 100644 --- a/src/reflex/scanners.ts +++ b/src/reflex/scanners.ts @@ -1,10 +1,11 @@ import fetch from "node-fetch"; import FormData from "form-data"; -import { writeFileSync, createReadStream } from "fs"; +import fs, { writeFileSync, createReadStream } from "fs"; import generateFileName from "../utils/temp/generateFileName.js"; import Tesseract from "node-tesseract-ocr"; import type Discord from "discord.js"; import client from "../utils/client.js"; +import { createHash } from "crypto"; interface NSFWSchema { nsfw: boolean; @@ -14,7 +15,11 @@ interface MalwareSchema { } export async function testNSFW(link: string): Promise { - const p = await saveAttachment(link); + const [p, hash] = await saveAttachment(link); + console.log("Checking an image") + let alreadyHaveCheck = await client.database.scanCache.read(hash) + if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data }; + console.log("Was not in db") const data = new FormData(); console.log(link); data.append("file", createReadStream(p)); @@ -32,13 +37,17 @@ export async function testNSFW(link: string): Promise { return { nsfw: false }; }); console.log(result); + client.database.scanCache.write(hash, result.nsfw); return { nsfw: result.nsfw }; } export async function testMalware(link: string): Promise { - const p = await saveAttachment(link); - const data = new FormData(); - data.append("file", createReadStream(p)); + const [p, hash] = await saveAttachment(link); + let alreadyHaveCheck = await client.database.scanCache.read(hash) + if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data }; + const data = new URLSearchParams(); + let f = createReadStream(p); + data.append("file", f.read(fs.statSync(p).size)); console.log(link); const result = await fetch("https://unscan.p.rapidapi.com/malware", { method: "POST", @@ -54,12 +63,15 @@ export async function testMalware(link: string): Promise { return { safe: true }; }); console.log(result); + client.database.scanCache.write(hash, result.safe); return { safe: result.safe }; } export async function testLink(link: string): Promise<{ safe: boolean; tags: string[] }> { console.log(link); - const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/malware", { + let alreadyHaveCheck = await client.database.scanCache.read(link) + if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data, tags: [] }; + const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/link", { method: "POST", headers: { "X-RapidAPI-Key": client.config.rapidApiKey, @@ -73,17 +85,18 @@ export async function testLink(link: string): Promise<{ safe: boolean; tags: str return { safe: true, tags: [] }; }); console.log(scanned); + client.database.scanCache.write(link, scanned.safe ?? true, []); return { safe: scanned.safe ?? true, tags: scanned.tags ?? [] }; } -export async function saveAttachment(link: string): Promise { +export async function saveAttachment(link: string): Promise<[string, string]> { const image = (await fetch(link)).arrayBuffer().toString(); const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!); writeFileSync(fileName, image, "base64"); - return fileName; + return [fileName, createHash('sha512').update(image, 'base64').digest('base64')]; } const linkTypes = { @@ -139,8 +152,7 @@ export async function LinkCheck(message: Discord.Message): Promise { export async function NSFWCheck(element: string): Promise { try { - const test = await testNSFW(element); - return test.nsfw; + return (await testNSFW(element)).nsfw; } catch { return false; } diff --git a/src/utils/client.ts b/src/utils/client.ts index 2a0702a..41cdbca 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -2,7 +2,7 @@ import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBit import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; -import { Guilds, History, ModNotes, Premium, PerformanceTest } from "../utils/database.js"; +import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache } from "../utils/database.js"; import EventScheduler from "../utils/eventScheduler.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js"; import config from "../config/main.json" assert { type: "json" }; @@ -22,6 +22,7 @@ class NucleusClient extends Client { premium: Premium; eventScheduler: EventScheduler; performanceTest: PerformanceTest; + scanCache: ScanCache; }; preloadPage: Record = {}; // e.g. { channelID: { command: privacy, page: 3}} commands: Record { + active = active ? "ACTIVE" : "INACTIVE"; + disabled = disabled ? "GREY." : ""; + if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active; + if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active; + if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active; + return "TRACKS.VERTICAL.MIDDLE." + disabled + active; +}; + +export const createVerticalTrack = (items: string[], active: boolean[], disabled?: boolean[]) => { + let out = ""; + if (!disabled) disabled = new Array(items.length).fill(false); + for (let i = 0; i < items.length; i++) { + out += getEmojiByName(verticalTrackIndicator(i, active[i] ?? false, items.length, disabled[i] ?? false)); + out += items[i] + "\n"; + } + return out; +} + export default pageIndicator; diff --git a/src/utils/database.ts b/src/utils/database.ts index 10b0ddb..c7b1777 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -148,6 +148,33 @@ export class History { } } +interface ScanCacheSchema { + addedAt: Date; + hash: string; + data: boolean; + tags: string[]; +} + +export class ScanCache { + scanCache: Collection; + + constructor() { + this.scanCache = database.collection("scanCache"); + } + + async read(hash: string) { + return await this.scanCache.findOne({ hash: hash }); + } + + async write(hash: string, data: boolean, tags?: string[]) { + await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); // TODO: cleanup function maybe + } + + async cleanup() { + await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} }); + } +} + export class PerformanceTest { performanceData: Collection; diff --git a/src/utils/ellipsis.ts b/src/utils/ellipsis.ts new file mode 100644 index 0000000..6ec5888 --- /dev/null +++ b/src/utils/ellipsis.ts @@ -0,0 +1,4 @@ +export default (str: string, max: number): string => { + if (str.length <= max) return str; + return str.slice(0, max - 3) + "..."; +} \ No newline at end of file diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts index 95761e9..17cfb1e 100644 --- a/src/utils/performanceTesting/record.ts +++ b/src/utils/performanceTesting/record.ts @@ -39,7 +39,7 @@ const record = async () => { singleNotify( "performanceTest", config.developmentGuildID, - `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${results.resources.memory}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``, + `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${Math.round(results.resources.memory)}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``, "Critical", config.owners ) diff --git a/tsconfig.json b/tsconfig.json index a39c584..7e6abdc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,6 @@ "skipLibCheck": true, "noImplicitReturns": false }, - "include": ["src/**/*"], + "include": ["src/**/*", "src/index.d.ts"], "exclude": ["src/Unfinished/**/*"] } From d5a67fb2e16014af9ab6f6235e79df6138022e83 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sun, 29 Jan 2023 15:44:53 -0500 Subject: [PATCH 22/74] worked on scanners, database, tracks, some moving around and cleaning up files. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 4af4c22..38073c6 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,3 @@ -? Role all +Role all (?) Server rules verificationRequired on welcome \ No newline at end of file From 4f79da1cdbd67895984b7c23914800d73959ff50 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 31 Jan 2023 16:50:37 -0500 Subject: [PATCH 23/74] worked on automod --- TODO.json | 5 -- src/commands/settings/filters.ts | 21 ----- src/commands/settings/filters/_meta.ts | 8 -- src/commands/settings/rolemenu.ts | 2 +- src/commands/settings/tracks.ts | 84 ++++++++++++------- src/config/default.json | 15 +++- src/config/emojis.json | 2 +- src/events/messageDelete.ts | 109 ++++++++++++------------- src/events/messageEdit.ts | 1 + src/index.ts | 2 +- src/premium/attachmentLogs.ts | 5 +- 11 files changed, 128 insertions(+), 126 deletions(-) delete mode 100644 src/commands/settings/filters.ts delete mode 100644 src/commands/settings/filters/_meta.ts diff --git a/TODO.json b/TODO.json index 4637953..90fe168 100644 --- a/TODO.json +++ b/TODO.json @@ -1,10 +1,5 @@ { "filters": { - "images": { - "NSFW": false, - "size": false - }, - "malware": false, "wordFilter": { "enabled": true, "words": { diff --git a/src/commands/settings/filters.ts b/src/commands/settings/filters.ts deleted file mode 100644 index 7636f91..0000000 --- a/src/commands/settings/filters.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type Discord from "discord.js"; -import type { CommandInteraction } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "discord.js"; - -const command = (builder: SlashCommandSubcommandBuilder) => - builder.setName("filter").setDescription("Setting for message filters"); - -const callback = async (_interaction: CommandInteraction): Promise => { - console.log("Filters"); -}; - -const check = (interaction: CommandInteraction, _partial: boolean = false) => { - const member = interaction.member as Discord.GuildMember; - if (!member.permissions.has("ManageMessages")) - return "You must have the *Manage Messages* permission to use this command"; - return true; -}; - -export { command }; -export { callback }; -export { check }; diff --git a/src/commands/settings/filters/_meta.ts b/src/commands/settings/filters/_meta.ts deleted file mode 100644 index d2aff53..0000000 --- a/src/commands/settings/filters/_meta.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { group } from "../../../utils/commandRegistration/slashCommandBuilder.js"; - -const name = "filters"; -const description = "Settings for filters"; - -const subcommand = await group(name, description, `settings/filters`) - -export { name, description, subcommand as command}; diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 9528183..90b224d 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -364,7 +364,7 @@ const callback = async (interaction: CommandInteraction): Promise => { new ButtonBuilder() .setCustomId("reorder") .setLabel("Reorder Pages") - .setEmoji(getEmojiByName("ICONS.SHUFFLE", "id") as APIMessageComponentEmoji) + .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji) .setStyle(ButtonStyle.Secondary) .setDisabled(Object.keys(currentObject).length <= 1), new ButtonBuilder() diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 782f52f..354a948 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, PermissionsBitField, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import client from "../../utils/client.js"; import createPageIndicator, { createVerticalTrack } from "../../utils/createPageIndicator.js"; import { LoadingEmbed } from "../../utils/defaults.js"; @@ -7,6 +7,8 @@ import getEmojiByName from "../../utils/getEmojiByName.js"; import ellipsis from "../../utils/ellipsis.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; +const { renderRole } = client.logger + const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("tracks") @@ -20,6 +22,7 @@ interface ObjectSchema { manageableBy: string[]; } + const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, current?: string) => { let name = current ?? ""; @@ -134,6 +137,7 @@ const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: } const editTrack = async (interaction: ButtonInteraction | StringSelectMenuInteraction, message: Message, roles: Collection, current?: ObjectSchema) => { + const isAdmin = (interaction.member!.permissions as PermissionsBitField).has("Administrator"); if(!current) { current = { name: "", @@ -143,40 +147,25 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera manageableBy: [] } } - const buttons = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId("back") - .setLabel("Back") - .setStyle(ButtonStyle.Secondary) - .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), - new ButtonBuilder() - .setCustomId("edit") - .setLabel("Edit Name") - .setStyle(ButtonStyle.Primary) - .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), - new ButtonBuilder() - .setCustomId("reorder") - .setLabel("Reorder") - .setStyle(ButtonStyle.Primary) - .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji), - ); + const roleSelect = new ActionRowBuilder() .addComponents( new RoleSelectMenuBuilder() .setCustomId("addRole") .setPlaceholder("Select a role to add") + .setDisabled(!isAdmin) ); let closed = false; do { const editableRoles: string[] = current.track.map((r) => { - if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position)) return r; + if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position)) return roles.get(r)!.name; }).filter(v => v !== undefined) as string[]; const selectMenu = new ActionRowBuilder() .addComponents( new StringSelectMenuBuilder() .setCustomId("removeRole") .setPlaceholder("Select a role to remove") + .setDisabled(!isAdmin) .addOptions( editableRoles.map((r, i) => { return new StringSelectMenuOptionBuilder() @@ -185,23 +174,56 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera ) ) ); + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit Name") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("reorder") + .setLabel("Reorder") + .setDisabled(!isAdmin) + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("retainPrevious") + .setLabel("Retain Previous") + .setStyle(current.retainPrevious ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("nullable") + .setLabel(`Role ${current.nullable ? "Not " : ""}Required`) + .setStyle(current.nullable ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL." + (current.nullable ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji) + ); + let allowed: boolean[] = []; for (const role of current.track) { const disabled: boolean = roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position; allowed.push(disabled) } + const mapped = current.track.map(role => roles.find(aRole => aRole.id === role)!); const embed = new EmojiEmbed() .setTitle("Tracks") .setDescription( `**Currently Editing:** ${current.name}\n\n` + - `${getEmojiByName} Members ${current.nullable ? "don't " : ""}need a role in this track` + - `${getEmojiByName} Members ${current.retainPrevious ? "don't " : ""}keep all roles below their current highest` + - createVerticalTrack(current.track, new Array(current.track.length).fill(false), allowed) + `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` + + `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` + + createVerticalTrack( + mapped.map(role => renderRole(role)), new Array(current.track.length).fill(false), allowed) ) + .setStatus("Success") - interaction.editReply({embeds: [embed], components: [buttons, roleSelect, selectMenu]}); + interaction.editReply({embeds: [embed], components: [roleSelect, selectMenu, buttons]}); let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null; @@ -227,6 +249,13 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera break; case "reorder": current.track = (await reorderTracks(out, message, roles, current.track))!; + break; + case "retainPrevious": + current.retainPrevious = !current.retainPrevious; + break; + case "nullable": + current.nullable = !current.nullable; + break; } } else if (out.isStringSelectMenu()) { out.deferUpdate(); @@ -258,8 +287,6 @@ const callback = async (interaction: CommandInteraction) => { const config = await client.database.guilds.read(interaction.guild!.id); const tracks: ObjectSchema[] = config.tracks; const roles = await interaction.guild!.roles.fetch(); - const memberRoles = interaction.member!.roles; - const member = interaction.member as GuildMember; let page = 0; let closed = false; @@ -329,10 +356,11 @@ const callback = async (interaction: CommandInteraction) => { } else { page = Math.min(page, Object.keys(tracks).length - 1); current = tracks[page]!; + const mapped = current.track.map(role => roles.find(aRole => aRole.id === role)!); embed.setDescription(`**Currently Editing:** ${current.name}\n\n` + `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` + `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` + - createVerticalTrack(current.track, new Array(current.track.length).fill(false)) + + createVerticalTrack(mapped.map(role => renderRole(role)), new Array(current.track.length).fill(false)) + `\n${createPageIndicator(config.tracks.length, page)}` ); @@ -372,7 +400,7 @@ const callback = async (interaction: CommandInteraction) => { page = tracks.length - 1; break; case "save": - // client.database.guilds.write(interaction.guild!.id, {"roleMenu.options": tracks}); // TODO + client.database.guilds.write(interaction.guild!.id, {tracks: tracks}); modified = false; break; } diff --git a/src/config/default.json b/src/config/default.json index 8e4197c..9129765 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -15,12 +15,17 @@ "words": { "strict": [], "loose": [] + }, + "allowed": { + "user": [], + "roles": [], + "channels": [] } }, "invite": { "enabled": false, "allowed": { - "users": [], + "user": [], "roles": [], "channels": [] } @@ -28,7 +33,13 @@ "pings": { "mass": 5, "everyone": true, - "roles": true + "roles": true, + "allowed": { + "user": [], + "roles": [], + "channels": [], + "rolesToMention": [] + } } }, "welcome": { diff --git a/src/config/emojis.json b/src/config/emojis.json index abeb52a..9ccb9fa 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -25,7 +25,7 @@ "ATTACHMENT": "997570687193587812", "LOGGING": "999613304446144562", "SAVE": "1065722246322200586", - "SHUFFLE": "1069323453909454890", + "REORDER": "1069323453909454890", "NOTIFY": { "ON": "1000726394579464232", "OFF": "1000726363495477368" diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index f8433fc..9563a33 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -4,61 +4,58 @@ import Discord, { AuditLogEvent, GuildAuditLogsEntry, Message, User } from "disc export const event = "messageDelete"; export async function callback(client: NucleusClient, message: Message) { - try { - if (message.author.id === client.user!.id) return; - if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return; - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; - const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd)) - .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0]; - if (auditLog) { - if (auditLog.createdTimestamp - 1000 < new Date().getTime()) return; - } - const replyTo = message.reference; - let content = message.cleanContent; - content.replace("`", "\\`"); - if (content.length > 256) content = content.substring(0, 253) + "..."; - const attachments = - message.attachments.size + ( - message.content.match( - /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi - ) ?? [] - ).length; - let attachmentJump = ""; - const config = (await client.database.guilds.read(message.guild!.id)).logging.attachments.saved[ - message.channel.id + message.id - ]; - if (config) { attachmentJump = ` [[View attachments]](${config})`; } - const data = { - meta: { - type: "messageDelete", - displayName: "Message Deleted", - calculateType: "messageDelete", - color: NucleusColors.red, - emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() - }, - separate: { - start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*" - }, - list: { - messageId: entry(message.id, `\`${message.id}\``), - sentBy: entry(message.author.id, renderUser(message.author)), - sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), - mentions: message.mentions.users.size, - attachments: entry(attachments, attachments + attachmentJump), - repliedTo: entry( - replyTo ? replyTo.messageId! : null, - replyTo ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${message.channel.id}/${replyTo.messageId})` - : "None" - ) - }, - hidden: { - guild: message.guild!.id - } - }; - log(data); - } catch (e) { - console.log(e); + if (message.author.id === client.user!.id) return; + if (message.author.bot) return; + if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return; + const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd)) + .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0]; + if (auditLog) { + if (auditLog.createdTimestamp - 1000 < new Date().getTime()) return; } + const replyTo = message.reference; + let content = message.cleanContent; + content.replace("`", "\\`"); + if (content.length > 256) content = content.substring(0, 253) + "..."; + const attachments = + message.attachments.size + ( + message.content.match( + /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi + ) ?? [] + ).length; + let attachmentJump = ""; + const config = (await client.database.guilds.read(message.guild!.id)).logging.attachments.saved[ + message.channel.id + message.id + ]; + if (config) { attachmentJump = ` [[View attachments]](${config})`; } + const data = { + meta: { + type: "messageDelete", + displayName: "Message Deleted", + calculateType: "messageDelete", + color: NucleusColors.red, + emoji: "MESSAGE.DELETE", + timestamp: new Date().getTime() + }, + separate: { + start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*" + }, + list: { + messageId: entry(message.id, `\`${message.id}\``), + sentBy: entry(message.author.id, renderUser(message.author)), + sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)), + deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + mentions: message.mentions.users.size, + attachments: entry(attachments, attachments + attachmentJump), + repliedTo: entry( + replyTo ? replyTo.messageId! : null, + replyTo ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${message.channel.id}/${replyTo.messageId})` + : "None" + ) + }, + hidden: { + guild: message.guild!.id + } + }; + log(data); } diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts index caa00f6..d491641 100644 --- a/src/events/messageEdit.ts +++ b/src/events/messageEdit.ts @@ -6,6 +6,7 @@ export const event = "messageUpdate"; export async function callback(client: NucleusClient, oldMessage: Message, newMessage: Message) { if (newMessage.author.id === client.user!.id) return; + if (newMessage.author.bot) return; if (!newMessage.guild) return; const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger; const replyTo: MessageReference | null = newMessage.reference; diff --git a/src/index.ts b/src/index.ts index 306811e..a3a8d38 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,4 +19,4 @@ process.on("uncaughtException", (err) => { console.error(err) }); await client.login(config.enableDevelopment ? config.developmentToken : config.token) -await recordPerformance(); \ No newline at end of file +await recordPerformance(); diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 628c2ec..078587e 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -13,7 +13,7 @@ export default async function logAttachment(message: Message): Promise file.local) }); - // await client.database.guilds.write(interaction.guild.id, {[`tags.${name}`]: value}); client.database.guilds.write(message.guild.id, { [`logging.attachments.saved.${message.channel.id}${message.id}`]: m.url }); From 8b3da21da2278f00e95fd483e9d888f606610f9b Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Thu, 2 Feb 2023 15:09:55 -0500 Subject: [PATCH 24/74] worked on automod --- src/commands/settings/automod.ts | 220 +++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/commands/settings/automod.ts diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts new file mode 100644 index 0000000..b9f2453 --- /dev/null +++ b/src/commands/settings/automod.ts @@ -0,0 +1,220 @@ +import type Discord from "discord.js"; +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from "discord.js"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import client from "../../utils/client.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; + +const command = (builder: SlashCommandSubcommandBuilder) => + builder.setName("automod").setDescription("Setting for automatic moderation features"); + + +const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id); + + +const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { + NSFW: boolean, + size: boolean +}): Promise<{NSFW: boolean, size: boolean}> => { + let closed = false; + do { + const options = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("nsfw") + .setLabel("NSFW") + .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("size") + .setLabel("Size") + .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji) + ) + + const embed = new EmojiEmbed() + .setTitle("Image Settings") + .setDescription( + `${emojiFromBoolean(current.NSFW)} **NSFW**\n` + + `${emojiFromBoolean(current.size)} **Size**\n` + ) + + await interaction.editReply({embeds: [embed], components: [options]}); + + let i: ButtonInteraction; + try { + i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction; + } catch (e) { + return current; + } + await i.deferUpdate(); + switch(i.customId) { + case "back": + closed = true; + break; + case "nsfw": + current.NSFW = !current.NSFW; + break; + case "size": + current.size = !current.size; + break; + } + } while(!closed); + return current; +} + +const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { + enabled: boolean, + words: {strict: string[], loose: string[]}, + allowed: {user: string[], roles: string[], channels: string[]} +}): Promise<{ + enabled: boolean, + words: {strict: string[], loose: string[]}, + allowed: {user: string[], roles: string[], channels: string[]} +}> => { + +} + +const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { + enabled: boolean, + allowed: {user: string[], roles: string[], channels: string[]} +}): Promise<{ + enabled: boolean, + allowed: {user: string[], roles: string[], channels: string[]} +}> => { + +} + +const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { + mass: number, + everyone: boolean, + roles: boolean, + allowed: { + roles: string[], + rolesToMention: string[], + users: string[], + channels: string[] + } +}): Promise<{ + mass: number, + everyone: boolean, + roles: boolean, + allowed: { + roles: string[], + rolesToMention: string[], + users: string[], + channels: string[] + } +}> => { + +} + +const callback = async (interaction: CommandInteraction): Promise => { + if (!interaction.guild) return; + const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true}); + const config = (await client.database.guilds.read(interaction.guild.id)).filters; + + let closed = false; + + const button = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + ) + + do { + + const selectMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("filter") + .setPlaceholder("Select a filter to edit") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Invites") + .setDescription("Automatically delete messages containing server invites") + .setValue("invites"), + new StringSelectMenuOptionBuilder() + .setLabel("Mentions") + .setDescription("Deletes messages with excessive mentions") + .setValue("mentions"), + new StringSelectMenuOptionBuilder() + .setLabel("Words") + .setDescription("Delete messages containing filtered words") + .setValue("words"), + new StringSelectMenuOptionBuilder() + .setLabel("Malware") + .setDescription("Automatically delete files and links containing malware") + .setValue("malware"), + new StringSelectMenuOptionBuilder() + .setLabel("Images") + .setDescription("Checks performed on images (NSFW, size checking, etc.)") + .setValue("images") + ) + ); + + const embed = new EmojiEmbed() + .setTitle("Automod Settings") + .setDescription( + `${emojiFromBoolean(config.invite.enabled)} **Invites**}\n` + + `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` + + `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` + + `${emojiFromBoolean(config.malware)} **Malware**\n` + + `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**}\n` + ) + + + await interaction.editReply({embeds: [embed], components: [selectMenu, button]}); + + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction; + } catch (e) { + closed = true; + return; + } + if(!i) return; + if(i.isButton()) { + await i.deferUpdate(); + await client.database.guilds.write(interaction.guild.id, {filters: config}); + } else { + switch(i.values[0]) { + case "invites": + break; + case "mentions": + break; + case "words": + break; + case "malware": + await i.deferUpdate(); + config.malware = !config.malware; + break; + case "images": + let next = await imageMenu(i, m, config.images); + if(next) config.images = next; + break; + } + } + + } while(!closed) + +}; + +const check = (interaction: CommandInteraction, _partial: boolean = false) => { + const member = interaction.member as Discord.GuildMember; + if (!member.permissions.has("ManageMessages")) + return "You must have the *Manage Messages* permission to use this command"; + return true; +}; + +export { command }; +export { callback }; +export { check }; From 486bca3c43c524c507a3b4288d259ebfdec4c655 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Thu, 2 Feb 2023 16:49:44 -0500 Subject: [PATCH 25/74] worked on automod settings --- src/commands/settings/automod.ts | 217 +++++++++++++++++++++++++++++-- src/utils/log.ts | 3 +- 2 files changed, 210 insertions(+), 10 deletions(-) diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index b9f2453..a7b6dbc 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -1,17 +1,114 @@ import type Discord from "discord.js"; -import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from "discord.js"; +import { ActionRowBuilder, AnyComponent, AnyComponentBuilder, AnySelectMenuInteraction, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, CommandInteraction, Guild, GuildMember, GuildTextBasedChannel, MentionableSelectMenuBuilder, Message, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SelectMenuBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; + const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("automod").setDescription("Setting for automatic moderation features"); const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id); +const listToAndMore = (list: string[], max: number) => { + // PineappleFan, Coded, Mini (and 10 more) + if(list.length > max) { + return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; + } + return list.join(", "); +} + +const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise => { + + const back = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)); + + let closed; + do { + let render: string[] = [] + let mapped: string[] = []; + let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder; + switch(type) { + case "member": + menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25); + mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" })); + render = ids.map(id => client.logger.renderUser(id)) + break; + case "role": + menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25); + mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name)) || "Unknown Role" })); + render = ids.map(id => client.logger.renderRole(id, interaction.guild!)) + break; + case "channel": + menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25); + mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name)) || "Unknown Channel" })); + render = ids.map(id => client.logger.renderChannel(id)) + break; + } + const removeOptions = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("remove") + .setPlaceholder("Remove") + .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!))) + .setDisabled(ids.length === 0) + ); + + const embed = new EmojiEmbed() + .setTitle(title) + .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN")) + .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None")) + .setStatus("Success"); + let components: ActionRowBuilder< + StringSelectMenuBuilder | + ButtonBuilder | + ChannelSelectMenuBuilder | + UserSelectMenuBuilder | + RoleSelectMenuBuilder + >[] = [new ActionRowBuilder().addComponents(menu)] + if(ids.length > 0) components.push(removeOptions); + components.push(back); + + await interaction.editReply({embeds: [embed], components: components}) + + let i: AnySelectMenuInteraction | ButtonInteraction; + try { + i = await interaction.channel!.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000}); + } catch(e) { + closed = true; + break; + } + + if(i.isButton()) { + await i.deferUpdate(); + if(i.customId === "back") { + closed = true; + break; + } + } else if(i.isStringSelectMenu()) { + await i.deferUpdate(); + if(i.customId === "remove") { + ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]); + if(ids.length === 0) { + menu.data.disabled = true; + } + } + } else { + await i.deferUpdate(); + if(i.customId === "user") { + ids = ids.concat((i as UserSelectMenuInteraction).values); + } else if(i.customId === "role") { + ids = ids.concat((i as RoleSelectMenuInteraction).values); + } else if(i.customId === "channel") { + ids = ids.concat((i as ChannelSelectMenuInteraction).values); + } + } + + } while(!closed) + return ids; +} const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { NSFW: boolean, @@ -72,23 +169,112 @@ const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, c const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { enabled: boolean, words: {strict: string[], loose: string[]}, - allowed: {user: string[], roles: string[], channels: string[]} + allowed: {users: string[], roles: string[], channels: string[]} }): Promise<{ enabled: boolean, words: {strict: string[], loose: string[]}, - allowed: {user: string[], roles: string[], channels: string[]} + allowed: {users: string[], roles: string[], channels: string[]} }> => { - + let closed = false; + do { + closed = true; + } while(!closed); + return current; } const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { enabled: boolean, - allowed: {user: string[], roles: string[], channels: string[]} + allowed: {users: string[], roles: string[], channels: string[]} }): Promise<{ enabled: boolean, - allowed: {user: string[], roles: string[], channels: string[]} + allowed: {users: string[], roles: string[], channels: string[]} }> => { + let closed = false; + do { + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("enabled") + .setLabel(current.enabled ? "Enabled" : "Disabled") + .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji) + ); + const menu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("toEdit") + .setPlaceholder("Edit your allow list") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Users") + .setDescription("Users that are allowed to send invites") + .setValue("users"), + new StringSelectMenuOptionBuilder() + .setLabel("Roles") + .setDescription("Roles that are allowed to send invites") + .setValue("roles"), + new StringSelectMenuOptionBuilder() + .setLabel("Channels") + .setDescription("Channels that anyone is allowed to send invites in") + .setValue("channels") + ).setDisabled(!current.enabled) + ) + + const embed = new EmojiEmbed() + .setTitle("Invite Settings") + .setDescription( + "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` + + `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` + + `**Users:** ` + listToAndMore(current.allowed.users.map(user => `> <@${user}>`), 5) + `\n` + + `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `> <@&${role}>`), 5) + `\n` + + `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `> <#${channel}>`), 5) + ) + .setStatus("Success") + .setEmoji("GUILD.SETTINGS.GREEN") + + + await interaction.editReply({embeds: [embed], components: [buttons, menu]}); + + let i: ButtonInteraction | StringSelectMenuInteraction; + try { + i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + return current; + } + + if(i.isButton()) { + await i.deferUpdate(); + switch(i.customId) { + case "back": + closed = true; + break; + case "enabled": + current.enabled = !current.enabled; + break; + } + } else { + await i.deferUpdate(); + switch(i.values[0]) { + case "users": + current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings"); + break; + case "roles": + current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings"); + break; + case "channels": + current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings"); + break; + } + } + + } while(!closed); + return current; } const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { @@ -112,7 +298,12 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, channels: string[] } }> => { - + let closed = false; + + do { + closed = true; + } while(!closed); + return current } const callback = async (interaction: CommandInteraction): Promise => { @@ -164,12 +355,14 @@ const callback = async (interaction: CommandInteraction): Promise => { const embed = new EmojiEmbed() .setTitle("Automod Settings") .setDescription( - `${emojiFromBoolean(config.invite.enabled)} **Invites**}\n` + + `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` + `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` + `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` + `${emojiFromBoolean(config.malware)} **Malware**\n` + - `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**}\n` + `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` ) + .setStatus("Success") + .setEmoji("GUILD.SETTINGS.GREEN") await interaction.editReply({embeds: [embed], components: [selectMenu, button]}); @@ -188,10 +381,16 @@ const callback = async (interaction: CommandInteraction): Promise => { } else { switch(i.values[0]) { case "invites": + await i.deferUpdate(); + config.invite = await inviteMenu(i, m, config.invite); break; case "mentions": + await i.deferUpdate(); + config.pings = await mentionMenu(i, m, config.pings); break; case "words": + await i.deferUpdate(); + config.wordFilter = await wordMenu(i, m, config.wordFilter); break; case "malware": await i.deferUpdate(); diff --git a/src/utils/log.ts b/src/utils/log.ts index 184f29a..4eff4e2 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -33,7 +33,8 @@ export const Logger = { if (typeof channel === "string") channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel; return `${channel.name} [<#${channel.id}>]`; }, - renderRole(role: Discord.Role) { + renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) { + if (typeof role === "string") role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role) as Discord.Role; return `${role.name} [<@&${role.id}>]`; }, renderEmoji(emoji: Discord.GuildEmoji) { From 5b53a8c56b2341b0979a05c3e8b24af6f348e4f4 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 3 Feb 2023 15:40:26 -0500 Subject: [PATCH 26/74] finished automod --- src/commands/settings/automod.ts | 350 ++++++++++++++++++++++++++++++- src/events/messageDelete.ts | 2 +- src/reflex/scanners.ts | 33 ++- src/utils/log.ts | 3 +- 4 files changed, 360 insertions(+), 28 deletions(-) diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index a7b6dbc..8e006b0 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -1,10 +1,34 @@ import type Discord from "discord.js"; -import { ActionRowBuilder, AnyComponent, AnyComponentBuilder, AnySelectMenuInteraction, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, CommandInteraction, Guild, GuildMember, GuildTextBasedChannel, MentionableSelectMenuBuilder, Message, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SelectMenuBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js"; +import { ActionRowBuilder, + AnySelectMenuInteraction, + APIMessageComponentEmoji, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + ChannelSelectMenuBuilder, + ChannelSelectMenuInteraction, + CommandInteraction, + Interaction, + Message, + MessageComponentInteraction, + ModalBuilder, + ModalSubmitInteraction, + RoleSelectMenuBuilder, + RoleSelectMenuInteraction, + StringSelectMenuBuilder, + StringSelectMenuInteraction, + StringSelectMenuOptionBuilder, + TextInputBuilder, + TextInputStyle, + UserSelectMenuBuilder, + UserSelectMenuInteraction +} from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -75,7 +99,7 @@ const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message let i: AnySelectMenuInteraction | ButtonInteraction; try { - i = await interaction.channel!.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000}); + i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000}); } catch(e) { closed = true; break; @@ -177,7 +201,152 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu }> => { let closed = false; do { - closed = true; + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("enabled") + .setLabel("Enabled") + .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji), + ); + + const selectMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("edit") + .setPlaceholder("Edit... ") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Words") + .setDescription("Edit your list of words to filter") + .setValue("words"), + new StringSelectMenuOptionBuilder() + .setLabel("Allowed Users") + .setDescription("Users who will be unaffected by the word filter") + .setValue("allowedUsers"), + new StringSelectMenuOptionBuilder() + .setLabel("Allowed Roles") + .setDescription("Roles that will be unaffected by the word filter") + .setValue("allowedRoles"), + new StringSelectMenuOptionBuilder() + .setLabel("Allowed Channels") + .setDescription("Channels where the word filter will not apply") + .setValue("allowedChannels") + ) + .setDisabled(!current.enabled) + ); + + const embed = new EmojiEmbed() + .setTitle("Word Filters") + .setDescription( + `${emojiFromBoolean(current.enabled)} **Enabled**\n` + + `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` + + `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` + + `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` + + `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` + + `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5) + ) + .setStatus("Success") + .setEmoji("GUILD.SETTINGS.GREEN") + + await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]}); + + let i: ButtonInteraction | StringSelectMenuInteraction; + try { + i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + + if(i.isButton()) { + await i.deferUpdate(); + switch(i.customId) { + case "back": + closed = true; + break; + case "enabled": + current.enabled = !current.enabled; + break; + } + } else { + switch(i.values[0]) { + case "words": + await interaction.editReply({embeds: [new EmojiEmbed() + .setTitle("Word Filter") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + .setEmoji("GUILD.SETTINGS.GREEN") + ], components: [new ActionRowBuilder().addComponents(new ButtonBuilder() + .setLabel("Back") + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("back") + )]}) + const modal = new ModalBuilder() + .setTitle("Word Filter") + .setCustomId("wordFilter") + .addComponents( + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("wordStrict") + .setLabel("Strict Words") + .setPlaceholder("Matches anywhere in the message, including surrounded by other characters") + .setValue(current.words.strict.join(", ") ?? "") + .setStyle(TextInputStyle.Paragraph) + .setRequired(false) + ), + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("wordLoose") + .setLabel("Loose Words") + .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation") + .setValue(current.words.loose.join(", ") ?? "") + .setStyle(TextInputStyle.Paragraph) + .setRequired(false) + ) + ) + + await i.showModal(modal); + let out; + try { + out = await modalInteractionCollector( + m, + (m: Interaction) => + (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, + (m) => m.customId === "back" + ); + } catch (e) { + break; + } + if (!out) break; + if(out.isButton()) break; + current.words.strict = out.fields.getTextInputValue("wordStrict") + .split(",").map(s => s.trim()).filter(s => s.length > 0); + current.words.loose = out.fields.getTextInputValue("wordLoose") + .split(",").map(s => s.trim()).filter(s => s.length > 0); + break; + case "allowedUsers": + await i.deferUpdate(); + current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter"); + break; + case "allowedRoles": + await i.deferUpdate(); + current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter"); + break; + case "allowedChannels": + await i.deferUpdate(); + current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter"); + break; + } + } } while(!closed); return current; } @@ -231,15 +400,15 @@ const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, .setDescription( "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` + `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` + - `**Users:** ` + listToAndMore(current.allowed.users.map(user => `> <@${user}>`), 5) + `\n` + - `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `> <@&${role}>`), 5) + `\n` + - `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `> <#${channel}>`), 5) + `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` + + `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` + + `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5) ) .setStatus("Success") .setEmoji("GUILD.SETTINGS.GREEN") - await interaction.editReply({embeds: [embed], components: [buttons, menu]}); + await interaction.editReply({embeds: [embed], components: [menu, buttons]}); let i: ButtonInteraction | StringSelectMenuInteraction; try { @@ -301,7 +470,172 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, let closed = false; do { - closed = true; + + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("everyone") + .setLabel(current.everyone ? "Everyone" : "No one") + .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("roles") + .setLabel(current.roles ? "Roles" : "No roles") + .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji) + ); + const menu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("toEdit") + .setPlaceholder("Edit mention settings") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Mass Mention Amount") + .setDescription("The amount of mentions before the bot will delete the message") + .setValue("mass"), + new StringSelectMenuOptionBuilder() + .setLabel("Roles") + .setDescription("Roles that are able to be mentioned") + .setValue("roles"), + ) + ) + + const allowedMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("allowed") + .setPlaceholder("Edit exceptions") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Users") + .setDescription("Users that are unaffected by the mention filter") + .setValue("users"), + new StringSelectMenuOptionBuilder() + .setLabel("Roles") + .setDescription("Roles that are unaffected by the mention filter") + .setValue("roles"), + new StringSelectMenuOptionBuilder() + .setLabel("Channels") + .setDescription("Channels where anyone is unaffected by the mention filter") + .setValue("channels") + ) + ) + + const embed = new EmojiEmbed() + .setTitle("Mention Settings") + .setDescription( + `Log when members mention:\n` + + `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` + + `${emojiFromBoolean(current.everyone)} **Everyone**\n` + + `${emojiFromBoolean(current.roles)} **Roles**\n` + + (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") + + "\n" + + `Except if...\n` + + `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` + + `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` + + `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}` + ) + .setStatus("Success") + .setEmoji("GUILD.SETTINGS.GREEN") + + await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]}); + + let i: ButtonInteraction | StringSelectMenuInteraction; + try { + i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + + if(i.isButton()) { + await i.deferUpdate(); + switch (i.customId) { + case "back": + closed = true; + break; + case "everyone": + current.everyone = !current.everyone; + break; + case "roles": + current.roles = !current.roles; + break; + } + } else { + switch (i.customId) { + case "toEdit": + switch (i.values[0]) { + case "mass": + await interaction.editReply({embeds: [new EmojiEmbed() + .setTitle("Word Filter") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + .setEmoji("GUILD.SETTINGS.GREEN") + ], components: [new ActionRowBuilder().addComponents(new ButtonBuilder() + .setLabel("Back") + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("back") + )]}) + const modal = new ModalBuilder() + .setTitle("Mass Mention Amount") + .setCustomId("mass") + .addComponents( + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("mass") + .setPlaceholder("Amount") + .setMinLength(1) + .setMaxLength(3) + .setStyle(TextInputStyle.Short) + ) + ) + await i.showModal(modal); + let out; + try { + out = await modalInteractionCollector( + m, + (m: Interaction) => + (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, + (m) => m.customId === "back" + ); + } catch (e) { + break; + } + if (!out) break; + if(out.isButton()) break; + current.mass = parseInt(out.fields.getTextInputValue("mass")); + break; + case "roles": + await i.deferUpdate(); + current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings"); + break; + } + break; + case "allowed": + await i.deferUpdate(); + switch (i.values[0]) { + case "users": + current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings"); + break; + case "roles": + current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings"); + break; + case "channels": + current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings"); + break; + } + break; + } + } + } while(!closed); return current } diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index 9563a33..65e1422 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -6,7 +6,7 @@ export const event = "messageDelete"; export async function callback(client: NucleusClient, message: Message) { if (message.author.id === client.user!.id) return; if (message.author.bot) return; - if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return; + if (client.noLog.includes(`${message.guild!.id}/${message.channel.id}/${message.id}`)) return; const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd)) .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0]; diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts index c8d59f0..c55e463 100644 --- a/src/reflex/scanners.ts +++ b/src/reflex/scanners.ts @@ -9,19 +9,18 @@ import { createHash } from "crypto"; interface NSFWSchema { nsfw: boolean; + errored?: boolean; } interface MalwareSchema { safe: boolean; + errored?: boolean; } export async function testNSFW(link: string): Promise { const [p, hash] = await saveAttachment(link); - console.log("Checking an image") let alreadyHaveCheck = await client.database.scanCache.read(hash) if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data }; - console.log("Was not in db") const data = new FormData(); - console.log(link); data.append("file", createReadStream(p)); const result = await fetch("https://unscan.p.rapidapi.com/", { method: "POST", @@ -31,13 +30,14 @@ export async function testNSFW(link: string): Promise { }, body: data }) - .then((response) => response.json() as Promise) + .then((response) => response.status === 200 ? response.json() as Promise : { nsfw: false, errored: true }) .catch((err) => { console.error(err); - return { nsfw: false }; + return { nsfw: false, errored: true }; }); - console.log(result); - client.database.scanCache.write(hash, result.nsfw); + if(!result.errored) { + client.database.scanCache.write(hash, result.nsfw); + } return { nsfw: result.nsfw }; } @@ -48,7 +48,6 @@ export async function testMalware(link: string): Promise { const data = new URLSearchParams(); let f = createReadStream(p); data.append("file", f.read(fs.statSync(p).size)); - console.log(link); const result = await fetch("https://unscan.p.rapidapi.com/malware", { method: "POST", headers: { @@ -57,18 +56,18 @@ export async function testMalware(link: string): Promise { }, body: data }) - .then((response) => response.json() as Promise) + .then((response) => response.status === 200 ? response.json() as Promise : { safe: true, errored: true }) .catch((err) => { console.error(err); - return { safe: true }; + return { safe: true, errored: true }; }); - console.log(result); - client.database.scanCache.write(hash, result.safe); + if (!result.errored) { + client.database.scanCache.write(hash, result.safe); + } return { safe: result.safe }; } export async function testLink(link: string): Promise<{ safe: boolean; tags: string[] }> { - console.log(link); let alreadyHaveCheck = await client.database.scanCache.read(link) if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data, tags: [] }; const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/link", { @@ -84,7 +83,6 @@ export async function testLink(link: string): Promise<{ safe: boolean; tags: str console.error(err); return { safe: true, tags: [] }; }); - console.log(scanned); client.database.scanCache.write(link, scanned.safe ?? true, []); return { safe: scanned.safe ?? true, @@ -93,10 +91,11 @@ export async function testLink(link: string): Promise<{ safe: boolean; tags: str } export async function saveAttachment(link: string): Promise<[string, string]> { - const image = (await fetch(link)).arrayBuffer().toString(); + const image = await (await fetch(link)).arrayBuffer() const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!); - writeFileSync(fileName, image, "base64"); - return [fileName, createHash('sha512').update(image, 'base64').digest('base64')]; + const enc = new TextDecoder("utf-8"); + writeFileSync(fileName, new DataView(image), "base64"); + return [fileName, createHash('sha512').update(enc.decode(image), 'base64').digest('base64')]; } const linkTypes = { diff --git a/src/utils/log.ts b/src/utils/log.ts index 4eff4e2..c5c7e06 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -10,7 +10,7 @@ const wait = promisify(setTimeout); export const Logger = { renderUser(user: Discord.User | string) { - if (typeof user === "string") return `${user} [<@${user}>]`; + if (typeof user === "string") user = client.users.cache.get(user) as Discord.User; return `${user.username} [<@${user.id}>]`; }, renderTime(t: number) { @@ -55,7 +55,6 @@ export const Logger = { const config = await client.database.guilds.read(log.hidden.guild); if (!config.logging.logs.enabled) return; if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) { - console.log("Not logging this type of event"); return; } if (config.logging.logs.channel) { From 161136741cfb770175f69d4c7603623b010fe358 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 3 Feb 2023 16:05:23 -0500 Subject: [PATCH 27/74] fixed createMessage.ts --- TODO.json | 20 -------------------- src/events/messageCreate.ts | 3 ++- 2 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 TODO.json diff --git a/TODO.json b/TODO.json deleted file mode 100644 index 90fe168..0000000 --- a/TODO.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "filters": { - "wordFilter": { - "enabled": true, - "words": { - "strict": [], - "loose": [] - } - }, - "invite": { - "enabled": false, - "channels": [] - }, - "pings": { - "mass": 5, - "everyone": true, - "roles": true - } - } -} diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 69bc542..d17dccb 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -78,7 +78,8 @@ export async function callback(_client: NucleusClient, message: Message) { if (fileNames.files.length > 0) { for (const element of fileNames.files) { const url = element.url ? element.url : element.local; - if (/\.(jpg|jpeg|png|gif|gifv|webm|webp|mp4|wav|mp3|ogg)$/.test(url)) { + if (/\.(j(pe?g|fif)|a?png|gifv?|w(eb[mp]|av)|mp([34]|eg-\d)|ogg|avi|h\.26(4|5)|cda)$/.test(url.toLowerCase())) { + // jpg|jpeg|png|apng|gif|gifv|webm|webp|mp4|wav|mp3|ogg|jfif|MPEG-#|avi|h.264|h.265 if ( config.filters.images.NSFW && !(message.channel instanceof ThreadChannel ? message.channel.parent?.nsfw : message.channel.nsfw) From 633866ff682adbb4233e974041c0db90446872dc Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 3 Feb 2023 17:06:00 -0500 Subject: [PATCH 28/74] worked on premium --- TODO | 3 +- src/commands/nucleus/premium.ts | 23 ++++++++------- src/reflex/scanners.ts | 6 ++-- src/utils/database.ts | 50 +++++++++++++++++++++++++++++---- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/TODO b/TODO index 38073c6..91eecea 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,4 @@ Role all (?) Server rules -verificationRequired on welcome \ No newline at end of file +verificationRequired on welcome +Role User GUI \ No newline at end of file diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 1c9db24..9d2c2b1 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -25,23 +25,23 @@ const callback = async (interaction: CommandInteraction): Promise => { ], components: [new ActionRowBuilder().addComponents(new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel("Join").setURL("https://discord.gg/bPaNnxe"))] }); return; } - const dbMember = await client.database.premium.fetchTotal(interaction.user.id) - let premium; + const dbMember = await client.database.premium.fetchUser(interaction.user.id) + let premium = `You do not have premium! You can't activate premium on any servers.`; let count = 0; - if (member.roles.cache.has("1066468879309750313")) { + const {level, appliesTo} = dbMember || {level: 0, appliesTo: []} + if (level === 99) { premium = `You have Infinite Premium! You have been gifted this by the developers as a thank you. You can give premium to any and all servers you are in.`; count = 200; - } else if (member.roles.cache.has("1066465491713003520")) { - premium = `You have Premium tier 1! You can give premium to ${1 - dbMember}.`; + } else if (level === 1) { + premium = `You have Premium tier 1! You can give premium to ${1 - appliesTo.length} more servers.`; count = 1; - } else if (member.roles.cache.has("1066439526496604194")) { - premium = `You have Premium tier 2! You can give premium to ${3 - dbMember}.`; + } else if (level === 2) { + premium = `You have Premium tier 2! You can give premium to ${3 - appliesTo.length} more servers.`; count = 3; - } else if (member.roles.cache.has("1066464134322978912")) { - premium = `You have Premium Mod! You already give premium to all servers you have a "manage" permission in.` + } else if (level === 3) { + premium = `You have Premium Mod! You can give premium to ${3 - appliesTo.length} more servers, as well as automatically giving premium to all servers you have a "manage" permission in.` count = 3; } - const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); let premiumGuild = "" if (hasPremium) { @@ -82,8 +82,7 @@ const callback = async (interaction: CommandInteraction): Promise => { if (i) { i.deferUpdate(); let guild = i.guild!; - let m = await client.database.premium.fetchTotal(interaction.user.id); - if (count - m <= 0) { + if (count - appliesTo.length <= 0) { interaction.editReply({ embeds: [ new EmojiEmbed() diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts index c55e463..b929d3d 100644 --- a/src/reflex/scanners.ts +++ b/src/reflex/scanners.ts @@ -1,5 +1,4 @@ import fetch from "node-fetch"; -import FormData from "form-data"; import fs, { writeFileSync, createReadStream } from "fs"; import generateFileName from "../utils/temp/generateFileName.js"; import Tesseract from "node-tesseract-ocr"; @@ -20,8 +19,9 @@ export async function testNSFW(link: string): Promise { const [p, hash] = await saveAttachment(link); let alreadyHaveCheck = await client.database.scanCache.read(hash) if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data }; - const data = new FormData(); - data.append("file", createReadStream(p)); + const data = new URLSearchParams(); + let r = createReadStream(p) + data.append("file", r.read(fs.statSync(p).size)); const result = await fetch("https://unscan.p.rapidapi.com/", { method: "POST", headers: { diff --git a/src/utils/database.ts b/src/utils/database.ts index c7b1777..c85b43a 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,6 +1,7 @@ import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; import config from "../config/main.json" assert { type: "json" }; +import client from "../utils/client.js"; const mongoClient = new MongoClient(config.mongoUrl); await mongoClient.connect(); @@ -230,18 +231,55 @@ export class Premium { this.premium = database.collection("premium"); } + async updateUser(user: string, level: number) { + if(!(await this.userExists(user))) await this.createUser(user, level); + await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true }); + } + + async userExists(user: string): Promise { + const entry = await this.premium.findOne({ user: user }); + return entry ? true : false; + } + + async createUser(user: string, level: number) { + await this.premium.insertOne({ user: user, appliesTo: [], level: level }); + } + async hasPremium(guild: string) { const entry = await this.premium.findOne({ appliesTo: { $in: [guild] } }); - if (!entry) return false; - return entry.expires.valueOf() > Date.now(); + return entry ? true : false; } - async fetchTotal(user: string): Promise { + async fetchUser(user: string): Promise { const entry = await this.premium.findOne({ user: user }); - if (!entry) return 0; - return entry.appliesTo.length; + if (!entry) return null; + return entry; + } + + async checkAllPremium() { + const entries = await this.premium.find({}).toArray(); + for(const {user, expiresAt} of entries) { + if(expiresAt) expiresAt < new Date().getTime() ? await this.premium.deleteOne({user: user}) : null; + const member = await (await client.guilds.fetch("684492926528651336")).members.fetch(user) + let level: number = 0; + if (member.roles.cache.has("1066468879309750313")) { + level = 99; + } else if (member.roles.cache.has("1066465491713003520")) { + level = 1; + } else if (member.roles.cache.has("1066439526496604194")) { + level = 2; + } else if (member.roles.cache.has("1066464134322978912")) { + level = 3; + } + + if (level > 0) { + await this.updateUser(user, level); + } else { + await this.premium.updateOne({ user: user }, { expiresAt: (new Date().getTime() + (1000*60*60*24*3)) }) + } + } } addPremium(user: string, guild: string) { @@ -409,6 +447,6 @@ export interface ModNoteSchema { export interface PremiumSchema { user: string; level: number; - expires: Date; appliesTo: string[]; + expiresAt?: number; } From b0d0c249cce511be187457b584cee806dcc11941 Mon Sep 17 00:00:00 2001 From: PineaFan Date: Sun, 5 Feb 2023 10:59:45 +0000 Subject: [PATCH 29/74] fixed loads of typing errors --- src/commands/help.ts | 31 ++--- src/commands/nucleus/premium.ts | 64 +++++----- src/commands/settings/automod.ts | 116 +++++++++++------- src/commands/settings/rolemenu.ts | 68 +++++----- src/commands/settings/stats.ts | 38 +++--- src/commands/settings/tickets.ts | 2 +- src/commands/settings/tracks.ts | 62 ++++++---- src/reflex/scanners.ts | 10 +- src/utils/client.ts | 2 +- .../slashCommandBuilder.ts | 4 +- src/utils/log.ts | 4 +- 11 files changed, 232 insertions(+), 169 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index a040358..e34500f 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -87,8 +87,8 @@ const callback = async (interaction: CommandInteraction): Promise => { `Select a command to get started${(interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``}` // FIXME ) } else { - let currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/')); - let current = commands.find((command) => command.name === currentPath[0])!; + const currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/')); + const current = commands.find((command) => command.name === currentPath[0])!; let optionString = `` let options: (ApplicationCommandOption & { @@ -97,16 +97,16 @@ const callback = async (interaction: CommandInteraction): Promise => { })[] = []; //options if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") { - let Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup - let Op2 = Op.options!.find(option => option.name === currentPath[2])! - options = Op2.options || [] + const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup + const Op2 = Op.options!.find(option => option.name === currentPath[2])! + options = Op2.options ?? [] } else if(currentPath[1] !== "" && currentPath[1] !== "none") { let Op = current.options.find(option => option.name === currentPath[1])! if(Op.type === ApplicationCommandOptionType.SubcommandGroup) { options = [] } else { Op = Op as ApplicationCommandSubCommand - options = Op.options || [] + options = Op.options ?? [] } } else { options = current.options.filter(option => option.type !== ApplicationCommandOptionType.SubcommandGroup && option.type !== ApplicationCommandOptionType.Subcommand) || []; @@ -117,7 +117,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0] let allowedToRun = true; if(APICommand?.check) { - APICommand?.check(interaction as Interaction, true) + allowedToRun = await APICommand.check(interaction as Interaction, true) } embed.setDescription( `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${currentData.mention}\n\n` + @@ -136,7 +136,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name)) ) if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) { - let subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand) || []; + const subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand) ?? []; subcommandRow.components[0]! .addOptions( new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"), @@ -152,7 +152,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } - let cmps = [commandRow]; + const cmps = [commandRow]; if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow); if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow); @@ -163,20 +163,23 @@ const callback = async (interaction: CommandInteraction): Promise => { i = await m.awaitMessageComponent({filter: (newInteraction) => interaction.user.id === newInteraction.user.id,time: 300000}) } catch (e) { closed = true; - break; + continue; } await i.deferUpdate(); - let value = i.values[0]!; + const value = i.values[0]!; switch(i.customId) { - case "commandRow": + case "commandRow": { currentPath = [value, "", ""]; break; - case "subcommandGroupRow": + } + case "subcommandGroupRow": { currentPath = [currentPath[0], value , ""]; break; - case "subcommandRow": + } + case "subcommandRow": { currentPath[2] = value; break; + } } } while (!closed); diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 9d2c2b1..2f20d2e 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -79,39 +79,37 @@ const callback = async (interaction: CommandInteraction): Promise => { } catch (e) { return; } - if (i) { - i.deferUpdate(); - let guild = i.guild!; - if (count - appliesTo.length <= 0) { - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Premium") - .setDescription( - `You have already activated premium on the maximum amount of servers!` + firstDescription - ) - .setEmoji("NUCLEUS.PREMIUMACTIVATE") - .setStatus("Danger") - ], - components: [] - }); - closed = true; - } else { - client.database.premium.addPremium(interaction.user.id, guild.id); - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Premium") - .setDescription( - `You have activated premium on this server!` + firstDescription - ) - .setEmoji("NUCLEUS.LOGO") - .setStatus("Danger") - ], - components: [] - }); - closed = true; - } + i.deferUpdate(); + const guild = i.guild!; + if (count - appliesTo.length <= 0) { + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + `You have already activated premium on the maximum amount of servers!` + firstDescription + ) + .setEmoji("NUCLEUS.PREMIUMACTIVATE") + .setStatus("Danger") + ], + components: [] + }); + closed = true; + } else { + client.database.premium.addPremium(interaction.user.id, guild.id); + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + `You have activated premium on this server!` + firstDescription + ) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ], + components: [] + }); + closed = true; } } while (!closed); diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index 8e006b0..ab5e037 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -48,28 +48,30 @@ const listToAndMore = (list: string[], max: number) => { const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise => { const back = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)); - let closed; do { let render: string[] = [] let mapped: string[] = []; let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder; switch(type) { - case "member": + case "member": { menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25); mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" })); render = ids.map(id => client.logger.renderUser(id)) break; - case "role": + } + case "role": { menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25); - mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name)) || "Unknown Role" })); + mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name ?? "Unknown Role"))})); render = ids.map(id => client.logger.renderRole(id, interaction.guild!)) break; - case "channel": + } + case "channel": { menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25); - mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name)) || "Unknown Channel" })); + mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name ?? "Unknown Role")) })); render = ids.map(id => client.logger.renderChannel(id)) break; + } } const removeOptions = new ActionRowBuilder() .addComponents( @@ -85,7 +87,7 @@ const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN")) .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None")) .setStatus("Success"); - let components: ActionRowBuilder< + const components: ActionRowBuilder< StringSelectMenuBuilder | ButtonBuilder | ChannelSelectMenuBuilder | @@ -102,7 +104,7 @@ const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000}); } catch(e) { closed = true; - break; + continue; } if(i.isButton()) { @@ -176,15 +178,18 @@ const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, c } await i.deferUpdate(); switch(i.customId) { - case "back": + case "back": { closed = true; break; - case "nsfw": + } + case "nsfw": { current.NSFW = !current.NSFW; break; - case "size": + } + case "size": { current.size = !current.size; break; + } } } while(!closed); return current; @@ -267,16 +272,18 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu if(i.isButton()) { await i.deferUpdate(); switch(i.customId) { - case "back": + case "back": { closed = true; break; - case "enabled": + } + case "enabled": { current.enabled = !current.enabled; break; + } } } else { switch(i.values[0]) { - case "words": + case "words": { await interaction.editReply({embeds: [new EmojiEmbed() .setTitle("Word Filter") .setDescription("Modal opened. If you can't see it, click back and try again.") @@ -298,7 +305,7 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu .setCustomId("wordStrict") .setLabel("Strict Words") .setPlaceholder("Matches anywhere in the message, including surrounded by other characters") - .setValue(current.words.strict.join(", ") ?? "") + .setValue(current.words.strict.join(", ")) .setStyle(TextInputStyle.Paragraph) .setRequired(false) ), @@ -308,7 +315,7 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu .setCustomId("wordLoose") .setLabel("Loose Words") .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation") - .setValue(current.words.loose.join(", ") ?? "") + .setValue(current.words.loose.join(", ")) .setStyle(TextInputStyle.Paragraph) .setRequired(false) ) @@ -333,18 +340,22 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu current.words.loose = out.fields.getTextInputValue("wordLoose") .split(",").map(s => s.trim()).filter(s => s.length > 0); break; - case "allowedUsers": + } + case "allowedUsers": { await i.deferUpdate(); current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter"); break; - case "allowedRoles": + } + case "allowedRoles": { await i.deferUpdate(); current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter"); break; - case "allowedChannels": + } + case "allowedChannels": { await i.deferUpdate(); current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter"); break; + } } } } while(!closed); @@ -420,25 +431,30 @@ const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, if(i.isButton()) { await i.deferUpdate(); switch(i.customId) { - case "back": + case "back": { closed = true; break; - case "enabled": + } + case "enabled": { current.enabled = !current.enabled; break; + } } } else { await i.deferUpdate(); switch(i.values[0]) { - case "users": + case "users": { current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings"); break; - case "roles": + } + case "roles": { current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings"); break; - case "channels": + } + case "channels": { current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings"); break; + } } } @@ -557,21 +573,24 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, if(i.isButton()) { await i.deferUpdate(); switch (i.customId) { - case "back": + case "back": { closed = true; break; - case "everyone": + } + case "everyone": { current.everyone = !current.everyone; break; - case "roles": + } + case "roles": { current.roles = !current.roles; break; + } } } else { switch (i.customId) { - case "toEdit": + case "toEdit": { switch (i.values[0]) { - case "mass": + case "mass": { await interaction.editReply({embeds: [new EmojiEmbed() .setTitle("Word Filter") .setDescription("Modal opened. If you can't see it, click back and try again.") @@ -613,26 +632,33 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, if(out.isButton()) break; current.mass = parseInt(out.fields.getTextInputValue("mass")); break; - case "roles": + } + case "roles": { await i.deferUpdate(); current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings"); break; + } } break; - case "allowed": + } + case "allowed": { await i.deferUpdate(); switch (i.values[0]) { - case "users": + case "users": { current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings"); break; - case "roles": + } + case "roles": { current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings"); break; - case "channels": + } + case "channels": { current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings"); break; + } } break; + } } } @@ -706,34 +732,38 @@ const callback = async (interaction: CommandInteraction): Promise => { i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction; } catch (e) { closed = true; - return; + continue; } - if(!i) return; if(i.isButton()) { await i.deferUpdate(); await client.database.guilds.write(interaction.guild.id, {filters: config}); } else { switch(i.values[0]) { - case "invites": + case "invites": { await i.deferUpdate(); config.invite = await inviteMenu(i, m, config.invite); break; - case "mentions": + } + case "mentions": { await i.deferUpdate(); config.pings = await mentionMenu(i, m, config.pings); break; - case "words": + } + case "words": { await i.deferUpdate(); config.wordFilter = await wordMenu(i, m, config.wordFilter); break; - case "malware": + } + case "malware": { await i.deferUpdate(); config.malware = !config.malware; break; - case "images": - let next = await imageMenu(i, m, config.images); - if(next) config.images = next; + } + case "images": { + const next = await imageMenu(i, m, config.images); + config.images = next; break; + } } } diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 90b224d..635a1fd 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -41,7 +41,7 @@ const defaultRolePageConfig = { } const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, currentObj: ObjectSchema[]) => { - let reorderRow = new ActionRowBuilder() + const reorderRow = new ActionRowBuilder() .addComponents( new StringSelectMenuBuilder() .setCustomId("reorder") @@ -55,7 +55,7 @@ const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, ) ) ); - let buttonRow = new ActionRowBuilder() + const buttonRow = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId("back") @@ -85,7 +85,6 @@ const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, if(!out) return; out.deferUpdate(); if (out.isButton()) return; - if(!out.values) return; const values = out.values; const newOrder: ObjectSchema[] = currentObj.map((_, i) => { @@ -156,7 +155,6 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele } if(!out) return [name, description]; if (out.isButton()) return [name, description]; - if(!out.fields) return [name, description]; name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name; description = out.fields.fields.find((f) => f.customId === "description")?.value ?? description; return [name, description] @@ -223,19 +221,22 @@ const editRoleMenuPage = async (interaction: StringSelectMenuInteraction | Butto } } else if (i.isButton()) { switch (i.customId) { - case "back": + case "back": { await i.deferUpdate(); back = true; break; - case "edit": - let [name, description] = await editNameDescription(i, interaction, m, data); + } + case "edit": { + const [name, description] = await editNameDescription(i, interaction, m, data); data.name = name ? name : data.name; data.description = description ? description : data.description; break; - case "addRole": + } + case "addRole": { await i.deferUpdate(); data.options.push(await createRoleMenuOptionPage(interaction, m)); break; + } } } @@ -247,9 +248,9 @@ const editRoleMenuPage = async (interaction: StringSelectMenuInteraction | Butto const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: {name: string; description: string | null; role: string}) => { const { renderRole} = client.logger; if (!data) data = { - name: "Role Menu Option", + name: "New role Menu Option", description: null, - role: "No role set" + role: "" }; let back = false; const buttons = new ActionRowBuilder() @@ -268,11 +269,11 @@ const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction do { const roleSelect = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder(data.role ? "Set role to" : "Set the role"); const embed = new EmojiEmbed() - .setTitle(`${data.name ?? "New Role Menu Option"}`) + .setTitle(`${data.name}`) .setStatus("Success") .setDescription( `**Description:**\n> ${data.description ?? "No description set"}\n\n` + - `**Role:** ${renderRole((await interaction.guild!.roles.fetch(data.role))!) ?? "No role set"}\n` + `**Role:** ${data.role ? renderRole((await interaction.guild!.roles.fetch(data.role))!) : "No role set"}\n` ) interaction.editReply({embeds: [embed], components: [new ActionRowBuilder().addComponents(roleSelect), buttons]}); @@ -292,16 +293,18 @@ const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction } } else if (i.isButton()) { switch (i.customId) { - case "back": + case "back": { await i.deferUpdate(); back = true; break; - case "edit": + } + case "edit": { await i.deferUpdate(); - let [name, description] = await editNameDescription(i, interaction, m, data as {name: string; description: string}); + const [name, description] = await editNameDescription(i, interaction, m, data as {name: string; description: string}); data.name = name ? name : data.name; data.description = description ? description : data.description; break; + } } } } while (!back); @@ -409,54 +412,63 @@ const callback = async (interaction: CommandInteraction): Promise => { i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; } catch (e) { closed = true; - break; + continue; } await i.deferUpdate(); if (i.isButton()) { switch (i.customId) { - case "back": + case "back": { page--; break; - case "next": + } + case "next": { page++; break; - case "add": - let newPage = await editRoleMenuPage(i, m) + } + case "add": { + const newPage = await editRoleMenuPage(i, m) if(!newPage) break; currentObject.push(); page = currentObject.length - 1; break; - case "reorder": - let reordered = await reorderRoleMenuPages(interaction, m, currentObject); + } + case "reorder": { + const reordered = await reorderRoleMenuPages(interaction, m, currentObject); if(!reordered) break; currentObject = reordered; break; - case "save": + } + case "save": { client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject}); modified = false; break; + } } } else if (i.isStringSelectMenu()) { switch (i.customId) { - case "action": + case "action": { switch(i.values[0]) { - case "edit": - let edited = await editRoleMenuPage(i, m, current!); + case "edit": { + const edited = await editRoleMenuPage(i, m, current!); if(!edited) break; currentObject[page] = edited; modified = true; break; - case "delete": + } + case "delete": { if(page === 0 && currentObject.keys.length - 1 > 0) page++; else page--; currentObject.splice(page, 1); break; + } } break; - case "page": + } + case "page": { page = parseInt(i.values[0]!); break; + } } } diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index dd4027d..f8a57b7 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -136,11 +136,12 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr } if (i.isButton()) { switch (i.customId) { - case "back": + case "back": { await i.deferUpdate(); closed = true; break; - case "save": + } + case "save": { await i.deferUpdate(); if (newChannel) { currentObject[newChannel] = { @@ -150,7 +151,8 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr } closed = true; break; - case "editName": + } + case "editName": { await interaction.editReply({ embeds: [new EmojiEmbed() .setTitle("Stats Channel") @@ -170,20 +172,21 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr }); showModal(i, {name: newChannelName, enabled: newChannelEnabled}) - const out = await modalInteractionCollector( + const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector( m, (m) => m.channel!.id === interaction.channel!.id && m.user!.id === interaction.user!.id, (i) => i.channel!.id === interaction.channel!.id && i.user!.id === interaction.user!.id && i.message!.id === m.id - ) as Discord.ModalSubmitInteraction | null; + ); if (!out) continue; - if (!out.fields) continue; if (out.isButton()) continue; newChannelName = out.fields.getTextInputValue("text"); break; - case "toggleEnabled": + } + case "toggleEnabled": { await i.deferUpdate(); newChannelEnabled = !newChannelEnabled; break; + } } } else { await i.deferUpdate(); @@ -307,11 +310,12 @@ const callback = async (interaction: CommandInteraction) => { if(i.isStringSelectMenu()) { switch(i.customId) { - case "page": + case "page": { await i.deferUpdate(); page = Object.keys(currentObject).indexOf(i.values[0]!); break; - case "action": + } + case "action": { modified = true; switch(i.values[0]!) { case "edit": { @@ -345,7 +349,6 @@ const callback = async (interaction: CommandInteraction) => { continue; } if (!out) continue - if (!out.fields) continue if (out.isButton()) continue; currentObject[Object.keys(currentObject)[page]!]!.name = out.fields.getTextInputValue("text"); break; @@ -358,32 +361,37 @@ const callback = async (interaction: CommandInteraction) => { } case "delete": { await i.deferUpdate(); - delete currentObject[Object.keys(currentObject)[page]!]; + currentObject = Object.fromEntries(Object.entries(currentObject).filter(([k]) => k !== Object.keys(currentObject)[page]!)); page = Math.min(page, Object.keys(currentObject).length - 1); modified = true; break; } } break; + } } } else { await i.deferUpdate(); switch(i.customId) { - case "back": + case "back": { page--; break; - case "next": + } + case "next": { page++; break; - case "add": + } + case "add": { currentObject = await addStatsChannel(interaction, m, currentObject); page = Object.keys(currentObject).length - 1; break; - case "save": + } + case "save": { client.database.guilds.write(interaction.guild.id, {stats: currentObject}); singleNotify("statsChannelDeleted", interaction.guild.id, true); modified = false; break; + } } } diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 3c5746c..29e6ea4 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -723,7 +723,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t data.customTypes.push(toAdd); } } else if ((i.component as ButtonComponent).customId === "switchToDefault") { - await i.deferUpdate(); + await i.deferUpdate(); await client.database.guilds.write(interaction.guild!.id, { "tickets.useCustom": false }, []); data.useCustom = false; } else if ((i.component as ButtonComponent).customId === "switchToCustom") { diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 354a948..4e27d68 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -37,7 +37,7 @@ const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInter .setCustomId("name") .setPlaceholder("Name here...") // TODO: Make better placeholder .setStyle(TextInputStyle.Short) - .setValue(name ?? "") + .setValue(name) .setRequired(true) ) ) @@ -74,14 +74,13 @@ const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInter } if(!out) return name; if (out.isButton()) return name; - if(!out.fields) return name; name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name; return name } const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: Collection, currentObj: string[]) => { - let reorderRow = new ActionRowBuilder() + const reorderRow = new ActionRowBuilder() .addComponents( new StringSelectMenuBuilder() .setCustomId("reorder") @@ -95,7 +94,7 @@ const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: ) ) ); - let buttonRow = new ActionRowBuilder() + const buttonRow = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId("back") @@ -125,7 +124,6 @@ const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: if(!out) return; out.deferUpdate(); if (out.isButton()) return; - if(!out.values) return; const values = out.values; const newOrder: string[] = currentObj.map((_, i) => { @@ -204,7 +202,7 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera .setEmoji(getEmojiByName("CONTROL." + (current.nullable ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji) ); - let allowed: boolean[] = []; + const allowed: boolean[] = []; for (const role of current.track) { const disabled: boolean = roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position; @@ -241,39 +239,46 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera if (out.isButton()) { out.deferUpdate(); switch(out.customId) { - case "back": + case "back": { closed = true; break; - case "edit": + } + case "edit": { current.name = (await editName(out, interaction, message, current.name))!; break; - case "reorder": + } + case "reorder": { current.track = (await reorderTracks(out, message, roles, current.track))!; break; - case "retainPrevious": + } + case "retainPrevious": { current.retainPrevious = !current.retainPrevious; break; - case "nullable": + } + case "nullable": { current.nullable = !current.nullable; break; + } } } else if (out.isStringSelectMenu()) { out.deferUpdate(); switch(out.customId) { - case "removeRole": + case "removeRole": { const index = current.track.findIndex(v => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]); current.track.splice(index, 1); break; + } } } else { switch(out.customId) { - case "addRole": + case "addRole": { const role = out.values![0]!; if(!current.track.includes(role)) { current.track.push(role); } out.reply({content: "That role is already on this track", ephemeral: true}) break; + } } } @@ -381,54 +386,61 @@ const callback = async (interaction: CommandInteraction) => { i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; } catch (e) { closed = true; - break; + continue; } await i.deferUpdate(); if (i.isButton()) { switch (i.customId) { - case "back": + case "back": { page--; break; - case "next": + } + case "next": { page++; break; - case "add": - let newPage = await editTrack(i, m, roles) + } + case "add": { + const newPage = await editTrack(i, m, roles) if(!newPage) break; tracks.push(); page = tracks.length - 1; break; - case "save": + } + case "save": { client.database.guilds.write(interaction.guild!.id, {tracks: tracks}); modified = false; break; + } } } else if (i.isStringSelectMenu()) { switch (i.customId) { - case "action": + case "action": { switch(i.values[0]) { - case "edit": - let edited = await editTrack(i, m, roles, current!); + case "edit": { + const edited = await editTrack(i, m, roles, current!); if(!edited) break; tracks[page] = edited; modified = true; break; - case "delete": + } + case "delete": { if(page === 0 && tracks.keys.length - 1 > 0) page++; else page--; tracks.splice(page, 1); break; + } } break; - case "page": + } + case "page": { page = parseInt(i.values[0]!); break; + } } } } while (!closed) - } const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts index b929d3d..cf713e6 100644 --- a/src/reflex/scanners.ts +++ b/src/reflex/scanners.ts @@ -17,10 +17,10 @@ interface MalwareSchema { export async function testNSFW(link: string): Promise { const [p, hash] = await saveAttachment(link); - let alreadyHaveCheck = await client.database.scanCache.read(hash) + const alreadyHaveCheck = await client.database.scanCache.read(hash) if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data }; const data = new URLSearchParams(); - let r = createReadStream(p) + const r = createReadStream(p) data.append("file", r.read(fs.statSync(p).size)); const result = await fetch("https://unscan.p.rapidapi.com/", { method: "POST", @@ -43,10 +43,10 @@ export async function testNSFW(link: string): Promise { export async function testMalware(link: string): Promise { const [p, hash] = await saveAttachment(link); - let alreadyHaveCheck = await client.database.scanCache.read(hash) + const alreadyHaveCheck = await client.database.scanCache.read(hash) if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data }; const data = new URLSearchParams(); - let f = createReadStream(p); + const f = createReadStream(p); data.append("file", f.read(fs.statSync(p).size)); const result = await fetch("https://unscan.p.rapidapi.com/malware", { method: "POST", @@ -68,7 +68,7 @@ export async function testMalware(link: string): Promise { } export async function testLink(link: string): Promise<{ safe: boolean; tags: string[] }> { - let alreadyHaveCheck = await client.database.scanCache.read(link) + const alreadyHaveCheck = await client.database.scanCache.read(link) if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data, tags: [] }; const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/link", { method: "POST", diff --git a/src/utils/client.ts b/src/utils/client.ts index 41cdbca..5286814 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -33,7 +33,7 @@ class NucleusClient extends Client { check: (interaction: Interaction, partial: boolean) => Promise | boolean, autocomplete: (interaction: AutocompleteInteraction) => Promise } | undefined,{name: string, description: string}]> = {}; - fetchedCommands: Collection = new Collection(); + fetchedCommands = new Collection(); constructor(database: typeof NucleusClient.prototype.database) { super({ intents: [ GatewayIntentBits.Guilds, diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index b215572..ef45875 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -32,7 +32,7 @@ export async function group( if (descriptionLocalizations) { subcommandGroup.setDescriptionLocalizations(descriptionLocalizations) } for (const subcommand of fetched.subcommands) { - let processedCommand = subcommand.command(new SlashCommandSubcommandBuilder()); + const processedCommand = subcommand.command(new SlashCommandSubcommandBuilder()); client.commands["commands/" + path + "/" + processedCommand.name] = [subcommand, { name: processedCommand.name, description: processedCommand.description }] subcommandGroup.addSubcommand(processedCommand); }; @@ -80,7 +80,7 @@ export async function command( command.addSubcommand(fetchedCommand); } for (const group of fetched.subcommandGroups) { - let processedCommand = group.command(new SlashCommandSubcommandGroupBuilder()); + const processedCommand = group.command(new SlashCommandSubcommandGroupBuilder()); client.commands[commandString! + "/" + processedCommand.name] = [undefined, { name: processedCommand.name, description: processedCommand.description }] command.addSubcommandGroup(processedCommand); }; diff --git a/src/utils/log.ts b/src/utils/log.ts index c5c7e06..64062ac 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -10,7 +10,7 @@ const wait = promisify(setTimeout); export const Logger = { renderUser(user: Discord.User | string) { - if (typeof user === "string") user = client.users.cache.get(user) as Discord.User; + if (typeof user === "string") user = client.users.cache.get(user)!; return `${user.username} [<@${user.id}>]`; }, renderTime(t: number) { @@ -34,7 +34,7 @@ export const Logger = { return `${channel.name} [<#${channel.id}>]`; }, renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) { - if (typeof role === "string") role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role) as Discord.Role; + if (typeof role === "string") role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role)!; return `${role.name} [<@&${role.id}>]`; }, renderEmoji(emoji: Discord.GuildEmoji) { From 4a958a1b224529186a0dda6467c7c7fbde1dbfbe Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 14 Feb 2023 13:41:51 -0500 Subject: [PATCH 30/74] completed user/role --- TODO | 3 +- src/commands/role/_meta.ts | 8 -- src/commands/role/user.ts | 100 ----------------------- src/commands/user/role.ts | 163 +++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 109 deletions(-) delete mode 100644 src/commands/role/_meta.ts delete mode 100644 src/commands/role/user.ts create mode 100644 src/commands/user/role.ts diff --git a/TODO b/TODO index 91eecea..9217e27 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ Role all (?) Server rules verificationRequired on welcome -Role User GUI \ No newline at end of file +Role User GUI +clean channels diff --git a/src/commands/role/_meta.ts b/src/commands/role/_meta.ts deleted file mode 100644 index f546d51..0000000 --- a/src/commands/role/_meta.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { command } from "../../utils/commandRegistration/slashCommandBuilder.js"; - -const name = "role"; -const description = "Change roles for users"; - -const subcommand = await command(name, description, `role`); - -export { name, description, subcommand as command }; diff --git a/src/commands/role/user.ts b/src/commands/role/user.ts deleted file mode 100644 index 4ec7f3e..0000000 --- a/src/commands/role/user.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { CommandInteraction, GuildMember, Role, User } from "discord.js"; -import type { SlashCommandSubcommandBuilder } from "discord.js"; -import client from "../../utils/client.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; -import keyValueList from "../../utils/generateKeyValueList.js"; -import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; - -const command = (builder: SlashCommandSubcommandBuilder) => - builder - .setName("user") - .setDescription("Gives or removes a role from someone") - .addUserOption((option) => - option.setName("user").setDescription("The member to give or remove the role from").setRequired(true) - ) - .addRoleOption((option) => - option.setName("role").setDescription("The role to give or remove").setRequired(true) - ) - .addStringOption((option) => - option - .setName("action") - .setDescription("The action to perform") - .setRequired(true) - .addChoices( - {name: "Add", value: "give"}, - {name: "Remove", value: "remove"} - ) - ); - -const callback = async (interaction: CommandInteraction): Promise => { - const { renderUser, renderRole } = client.logger; - const action = interaction.options.get("action")?.value as string; - const role: Role = (await interaction.guild!.roles.fetch(interaction.options.get("role")?.value as string))!; - // TODO:[Modals] Replace this with a modal - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Role") - .setDescription( - keyValueList({ - user: renderUser(interaction.options.getUser("user")! as User), - role: renderRole(role) - }) + - `\nAre you sure you want to ${ - action === "give" ? "give the role to" : "remove the role from" - } ${interaction.options.getUser("user")}?` - ) - .setColor("Danger") - .setFailedMessage("No changes were made", "Success", "GUILD.ROLES.CREATE") - .send(); - if (confirmation.cancelled || !confirmation.success) return; - try { - const member = interaction.options.getMember("user") as GuildMember; - if ((interaction.options.get("action")?.value as string) === "give") { - member.roles.add(role); - } else { - member.roles.remove(role); - } - } catch (e) { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Role") - .setDescription("Something went wrong and the role could not be added") - .setStatus("Danger") - .setEmoji("CONTROL.BLOCKCROSS") - ], - components: [] - }); - } - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Role") - .setDescription(`The role has been ${action === "give" ? "given" : "removed"} successfully`) - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [] - }); -}; - -const check = (interaction: CommandInteraction, partial: boolean = false) => { - const member = interaction.member as GuildMember; - // Check if the user has manage_roles permission - if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; - if (partial) return true; - if (!interaction.guild) return - const me = interaction.guild.members.me!; - const apply = interaction.options.getMember("user") as GuildMember | null; - if (apply === null) return "That member is not in the server"; - // Check if Nucleus has permission to role - if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission"; - // Allow the owner to role anyone - if (member.id === interaction.guild.ownerId) return true; - // Allow role - return true; -}; - -export { command }; -export { callback }; -export { check }; diff --git a/src/commands/user/role.ts b/src/commands/user/role.ts new file mode 100644 index 0000000..19bd3c7 --- /dev/null +++ b/src/commands/user/role.ts @@ -0,0 +1,163 @@ +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; +import client from "../../utils/client.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; + +const listToAndMore = (list: string[], max: number) => { + // PineappleFan, Coded, Mini (and 10 more) + if(list.length > max) { + return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; + } + return list.join(", "); +} + +const { renderUser } = client.logger; + +const canEdit = (role: Role, member: GuildMember, me: GuildMember): [string, boolean] => { + if(role.position >= me.roles.highest.position || + role.position >= member.roles.highest.position + ) return [`~~<@&${role.id}>~~`, false]; + return [`<@&${role.id}>`, true]; +}; + +const command = (builder: SlashCommandSubcommandBuilder) => + builder + .setName("role") + .setDescription("Gives or removes a role from someone") + .addUserOption((option) => option.setName("user").setDescription("The user to give or remove the role from")) + +const callback = async (interaction: CommandInteraction): Promise => { + const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true }); + + let member = interaction.options.getMember("user") as GuildMember | null; + + if(!member) { + let memberEmbed = new EmojiEmbed() + .setTitle("Role") + .setDescription(`Please choose a member to edit the roles of.`) + .setEmoji("GUILD.ROLES.CREATE") + .setStatus("Success"); + let memberChooser = new ActionRowBuilder().addComponents( + new UserSelectMenuBuilder() + .setCustomId("memberChooser") + .setPlaceholder("Select a member") + ); + await interaction.editReply({embeds: [memberEmbed], components: [memberChooser]}); + + const filter = (i: UserSelectMenuInteraction) => i.customId === "memberChooser" && i.user.id === interaction.user.id; + + let i: UserSelectMenuInteraction | null; + try { + i = await m.awaitMessageComponent<5>({ filter, time: 300000}); + } catch (e) { + return; + } + + if(!i) return; + memberEmbed.setDescription(`Editing roles for ${renderUser(i.values[0]!)}`); + await i.deferUpdate(); + await interaction.editReply({ embeds: LoadingEmbed, components: [] }) + member = await interaction.guild?.members.fetch(i.values[0]!)!; + + } + + let closed = false; + let rolesToChange: string[] = []; + const roleAdd = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("roleAdd") + .setPlaceholder("Select a role to add") + .setMaxValues(25) + ); + + do { + const embed = new EmojiEmbed() + .setTitle("Role") + .setDescription( + `${getEmojiByName("ICONS.EDIT")} Editing roles for <@${member.id}>\n\n` + + `Adding:\n` + + `${listToAndMore(rolesToChange.filter((r) => !member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0]) || ["None"], 5)}\n` + + `Removing:\n` + + `${listToAndMore(rolesToChange.filter((r) => member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0]) || ["None"], 5)}\n` + ) + .setEmoji("GUILD.ROLES.CREATE") + .setStatus("Success"); + + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("roleSave") + .setLabel("Apply") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId("roleDiscard") + .setLabel("Reset") + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Danger) + ); + + await interaction.editReply({ embeds: [embed], components: [roleAdd, buttons] }); + + let i: RoleSelectMenuInteraction | ButtonInteraction | null; + try { + i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 }) as RoleSelectMenuInteraction | ButtonInteraction; + } catch (e) { + return; + } + + if(!i) return; + i.deferUpdate(); + if(i.isButton()) { + switch(i.customId) { + case "roleSave": + const roles = rolesToChange.map((r) => interaction.guild?.roles.cache.get(r)!); + await interaction.editReply({ embeds: LoadingEmbed, components: [] }); + let rolesToAdd: Role[] = []; + let rolesToRemove: Role[] = []; + for(const role of roles) { + if(!canEdit(role, interaction.member as GuildMember, interaction.guild?.members.me!)[1]) continue; + if(member.roles.cache.has(role.id)) { + rolesToRemove.push(role); + } else { + rolesToAdd.push(role); + } + } + await member.roles.add(rolesToAdd); + await member.roles.remove(rolesToRemove); + rolesToChange = []; + break; + case "roleDiscard": + rolesToChange = []; + await interaction.editReply({ embeds: LoadingEmbed, components: [] }); + break; + } + } else { + rolesToChange = i.values; + } + + } while (!closed); + +}; + +const check = (interaction: CommandInteraction, partial: boolean = false) => { + const member = interaction.member as GuildMember; + // Check if the user has manage_roles permission + if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission"; + if (partial) return true; + if (!interaction.guild) return + const me = interaction.guild.members.me!; + // Check if Nucleus has permission to role + if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission"; + // Allow the owner to role anyone + if (member.id === interaction.guild.ownerId) return true; + // Allow role + return true; +}; + +export { command }; +export { callback }; +export { check }; From ad0b82065f810bce099a7de90364c7d06a442c2c Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 14 Feb 2023 14:27:09 -0500 Subject: [PATCH 31/74] added channel cleaning --- TODO | 1 - src/commands/settings/automod.ts | 166 +++++++++++++++++++++++++++++-- src/config/default.json | 7 ++ src/events/messageCreate.ts | 6 ++ src/utils/database.ts | 7 ++ 5 files changed, 179 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index 9217e27..c7a52b0 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ Role all (?) Server rules verificationRequired on welcome -Role User GUI clean channels diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index ab5e037..d3d24c9 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -666,6 +666,152 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, return current } +const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { + channels: string[], + allowed: { + roles: string[], + user: string[] + } +}): Promise<{ + channels: string[], + allowed: { + roles: string[], + user: string[] + } +}> => { + let closed = false; + if(!current) current = {channels: [], allowed: {roles: [], user: []}}; + if(!current.channels) current.channels = []; + if(!current.allowed) current.allowed = {roles: [], user: []}; + + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("toAdd") + .setPlaceholder("Select a channel") + ) + + const allowedMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("allowed") + .setPlaceholder("Edit exceptions") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Users") + .setDescription("Users that are unaffected by the mention filter") + .setValue("users"), + new StringSelectMenuOptionBuilder() + .setLabel("Roles") + .setDescription("Roles that are unaffected by the mention filter") + .setValue("roles") + ) + ) + + do { + + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + ) + + const embed = new EmojiEmbed() + .setTitle("Clean Settings") + .setEmoji("GUILD.SETTINGS.GREEN") + .setDescription( + `Current clean channels:\n\n` + + `${current.channels.length > 0 ? listToAndMore(current.channels.map(c => `<#${c}>`), 10) : "None"}\n\n` + ) + .setStatus("Success") + + + await interaction.editReply({embeds: [embed], components: [channelMenu, allowedMenu, buttons]}); + + let i: ButtonInteraction | ChannelSelectMenuInteraction; + try { + i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | ChannelSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + await i.deferUpdate(); + if(i.isButton()) { + switch (i.customId) { + case "back": { + closed = true; + break; + } + } + } else { + switch (i.customId) { + case "toAdd": { + let channelEmbed = new EmojiEmbed() + .setTitle("Clean Settings") + .setDescription(`Editing <#${i.values[0]}>`) + .setEmoji("GUILD.SETTINGS.GREEN") + .setStatus("Success") + let channelButtons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")), + new ButtonBuilder() + .setCustomId("switch") + .setLabel(current.channels.includes(i.values[0]!) ? "Remove" : "Add") + .setStyle(current.channels.includes(i.values[0]!) ? ButtonStyle.Danger : ButtonStyle.Success) + ) + + await i.editReply({embeds: [channelEmbed], components: [channelButtons]}); + let j: ButtonInteraction; + try { + j = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction; + } catch (e) { + closed = true; + break; + } + await j.deferUpdate(); + switch (j.customId) { + case "back": { + break; + } + case "switch": { + if(current.channels.includes(i.values[0]!)) { + current.channels.splice(current.channels.indexOf(i.values[0]!), 1); + } else { + current.channels.push(i.values[0]!); + } + } + } + break; + } + case "allowed": { + switch (i.values[0]) { + case "users": { + current.allowed.user = await toSelectMenu(interaction, m, current.allowed.user, "member", "Mention Settings"); + break; + } + case "roles": { + current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings"); + break; + } + } + break; + } + } + } + + } while(!closed); + + return current; + +} + const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true}); @@ -708,7 +854,11 @@ const callback = async (interaction: CommandInteraction): Promise => { new StringSelectMenuOptionBuilder() .setLabel("Images") .setDescription("Checks performed on images (NSFW, size checking, etc.)") - .setValue("images") + .setValue("images"), + new StringSelectMenuOptionBuilder() + .setLabel("Clean") + .setDescription("Automatically delete new messages in specific channels") + .setValue("clean") ) ); @@ -719,7 +869,8 @@ const callback = async (interaction: CommandInteraction): Promise => { `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` + `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` + `${emojiFromBoolean(config.malware)} **Malware**\n` + - `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` + `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` + + `${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n` ) .setStatus("Success") .setEmoji("GUILD.SETTINGS.GREEN") @@ -734,28 +885,24 @@ const callback = async (interaction: CommandInteraction): Promise => { closed = true; continue; } + await i.deferUpdate(); if(i.isButton()) { - await i.deferUpdate(); await client.database.guilds.write(interaction.guild.id, {filters: config}); } else { switch(i.values[0]) { case "invites": { - await i.deferUpdate(); config.invite = await inviteMenu(i, m, config.invite); break; } case "mentions": { - await i.deferUpdate(); config.pings = await mentionMenu(i, m, config.pings); break; } case "words": { - await i.deferUpdate(); config.wordFilter = await wordMenu(i, m, config.wordFilter); break; } case "malware": { - await i.deferUpdate(); config.malware = !config.malware; break; } @@ -764,6 +911,11 @@ const callback = async (interaction: CommandInteraction): Promise => { config.images = next; break; } + case "clean": { + const next = await cleanMenu(i, m, config.clean); + config.clean = next; + break; + } } } diff --git a/src/config/default.json b/src/config/default.json index 9129765..e972e93 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -40,6 +40,13 @@ "channels": [], "rolesToMention": [] } + }, + "clean": { + "channels": [], + "allowed": { + "user": [], + "roles": [] + } } }, "welcome": { diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index d17dccb..804e6ea 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -25,6 +25,12 @@ export async function callback(_client: NucleusClient, message: Message) { const content = message.content.toLowerCase() || ""; const config = await client.memory.readGuildInfo(message.guild.id); + if(config.filters.clean.channels.includes(message.channel.id)) { + let memberRoles = message.member!.roles.cache.map(role => role.id); + let roleAllow = config.filters.clean.allowed.roles.some(role => memberRoles.includes(role)); + let userAllow = config.filters.clean.allowed.user.includes(message.author.id); + if(!roleAllow && !userAllow) return await message.delete(); + } const filter = getEmojiByName("ICONS.FILTER"); let attachmentJump = ""; if (config.logging.attachments.saved[message.channel.id + message.id]) { diff --git a/src/utils/database.ts b/src/utils/database.ts index c85b43a..7deb3c1 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -332,6 +332,13 @@ export interface GuildConfig { channels: string[]; }; }; + clean: { + channels: string[]; + allowed: { + user: string[]; + roles: string[]; + } + } }; welcome: { enabled: boolean; From beeeeae05bb42a0ca3fd0d85ca3780352375da27 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 14 Feb 2023 14:31:34 -0500 Subject: [PATCH 32/74] added channel cleaning --- TODO | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TODO b/TODO index c7a52b0..38073c6 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ Role all (?) Server rules -verificationRequired on welcome -clean channels +verificationRequired on welcome \ No newline at end of file From 2e54a775c2ce67d106be35d5bae739e6ce60268d Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 14 Feb 2023 16:26:47 -0500 Subject: [PATCH 33/74] changing transcript saving --- TODO | 1 - src/commands/mod/ban.ts | 2 +- src/premium/createTranscript.ts | 130 +++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 38073c6..2a0d7be 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,2 @@ -Role all (?) Server rules verificationRequired on welcome \ No newline at end of file diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 7e67222..b500642 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -25,7 +25,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; const { renderUser } = client.logger; - // TODO:[Modals] Replace this with a modal + // TODO:[Modals] Replace the command arguments with a modal let reason = null; let notify = true; let confirmation; diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 754a06b..43de767 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -7,7 +7,8 @@ import { MessageComponentInteraction, TextChannel, ButtonStyle, - User + User, + ComponentType } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; @@ -16,6 +17,64 @@ import client from "../utils/client.js"; const pbClient = new PasteClient(client.config.pastebinApiKey); +interface TranscriptEmbed { + title?: string; + description?: string; + fields?: { + name: string; + value: string; + inline: boolean; + }[]; + footer?: { + text: string; + iconURL?: string; + }; +} + +interface TranscriptComponent { + type: number; + style?: ButtonStyle; + label?: string; + description?: string; + placeholder?: string; + emojiURL?: string; +} + +interface TranscriptAuthor { + username: string; + discriminator: number; + nickname?: string; + id: string; + iconURL?: string; + topRole: { + color: number; + badgeURL?: string; + } +} + +interface TranscriptMessage { + id: string; + author: TranscriptAuthor; + content?: string; + embeds?: TranscriptEmbed[]; + components?: TranscriptComponent[][]; + editedTimestamp?: number; + createdTimestamp: number; + flags?: string[]; + attachments?: unknown; //FIXME + stickerURLs?: string[]; + referencedMessage?: string | [string, string, string]; +} + +interface Transcript { + type: "ticket" | "purge" + guild: string; + channel: string; + messages: TranscriptMessage[]; + createdTimestamp: number; + createdBy: TranscriptAuthor; +} + export default async function (interaction: CommandInteraction | MessageComponentInteraction) { if (interaction.channel === null) return; if (!(interaction.channel instanceof TextChannel)) return; @@ -45,6 +104,75 @@ export default async function (interaction: CommandInteraction | MessageComponen out += "\n\n"; } }); + + let interactionMember = await interaction.guild?.members.fetch(interaction.user.id) + + let newOut: Transcript = { + type: "ticket", + guild: interaction.guild!.id, + channel: interaction.channel!.id, + messages: [], + createdTimestamp: Date.now(), + createdBy: { + username: interaction.user.username, + discriminator: parseInt(interaction.user.discriminator), + id: interaction.user.id, + topRole: { + color: interactionMember?.roles.highest.color ?? 0x000000 + } + } + } + if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; + messages.reverse().forEach((message) => { + let msg: TranscriptMessage = { + id: message.id, + author: { + username: message.author.username, + discriminator: parseInt(message.author.discriminator), + id: message.author.id, + topRole: { + color: message.member!.roles.highest.color + } + }, + createdTimestamp: message.createdTimestamp + }; + if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; + if (message.content) msg.content = message.content; + if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { + let obj: TranscriptEmbed = {}; + if (embed.title) obj.title = embed.title; + if (embed.description) obj.description = embed.description; + if (embed.fields.length > 0) obj.fields = embed.fields.map(field => { + return { + name: field.name, + value: field.value, + inline: field.inline ?? false + } + }); + if (embed.footer) obj.footer = { + text: embed.footer.text, + }; + if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL; + return obj; + }); + if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { + let obj: TranscriptComponent = { + type: child.type + } + if (child.type === ComponentType.Button) { + obj.style = child.style; + obj.label = child.label ?? ""; + } + return obj + })); + if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp; + if (message.flags) msg.flags = message.flags.toArray(); + + if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); + if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; + + }); + const topic = interaction.channel.topic; let member: GuildMember | null = null; if (topic !== null) { From 362c51d71ee909229d8391059cb775b02aadfdec Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 14 Feb 2023 16:31:33 -0500 Subject: [PATCH 34/74] changing transcript saving --- src/premium/createTranscript.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 43de767..54b79ea 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -8,7 +8,9 @@ import { TextChannel, ButtonStyle, User, - ComponentType + ComponentType, + APIBaseSelectMenuComponent, + SelectMenuComponent } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; @@ -162,6 +164,8 @@ export default async function (interaction: CommandInteraction | MessageComponen if (child.type === ComponentType.Button) { obj.style = child.style; obj.label = child.label ?? ""; + } else if (child.type > 2) { + obj.placeholder = child.placeholder ?? ""; } return obj })); From 1f67504a24a36a169911722c561d2d61ba11e62e Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Thu, 16 Feb 2023 17:01:29 -0500 Subject: [PATCH 35/74] fixed settings commands, todo's --- src/Unfinished/all.ts | 2 +- src/actions/tickets/create.ts | 2 +- src/commands/help.ts | 9 +- src/commands/mod/mute.ts | 2 +- src/commands/mod/slowmode.ts | 2 +- src/commands/privacy.ts | 43 +- src/commands/settings/automod.ts | 9 +- src/commands/settings/logs/events.ts | 2 +- .../settings/logs/{channel.ts => general.ts} | 0 .../settings/logs/{staff.ts => warnings.ts} | 0 .../settings/{commands.ts => moderation.ts} | 61 +- src/commands/settings/rolemenu.ts | 4 +- src/commands/settings/tickets.ts | 543 ++++-------------- src/commands/settings/tracks.ts | 2 +- src/commands/settings/verify.ts | 202 +------ src/commands/user/about.ts | 7 +- src/commands/user/role.ts | 9 +- src/config/emojis.json | 5 +- src/events/guildMemberUpdate.ts | 2 +- src/events/roleUpdate.ts | 8 +- src/premium/attachmentLogs.ts | 2 +- src/premium/createTranscript.ts | 13 +- src/reflex/guide.ts | 2 +- src/reflex/verify.ts | 5 +- src/utils/database.ts | 2 +- src/utils/listToAndMore.ts | 7 + 26 files changed, 204 insertions(+), 741 deletions(-) rename src/commands/settings/logs/{channel.ts => general.ts} (100%) rename src/commands/settings/logs/{staff.ts => warnings.ts} (100%) rename src/commands/settings/{commands.ts => moderation.ts} (79%) create mode 100644 src/utils/listToAndMore.ts diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts index a6379b7..eea33f5 100644 --- a/src/Unfinished/all.ts +++ b/src/Unfinished/all.ts @@ -16,7 +16,7 @@ import addPlural from "../utils/plurals.js"; import client from "../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => - builder // TODO: DON'T RELEASE THIS + builder .setName("all") .setDescription("Gives or removes a role from everyone"); diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts index 3e5cacd..935f4ae 100644 --- a/src/actions/tickets/create.ts +++ b/src/actions/tickets/create.ts @@ -257,7 +257,7 @@ export default async function (interaction: CommandInteraction | ButtonInteracti type: Discord.ChannelType.PrivateThread, reason: "Creating ticket" }) as Discord.PrivateThreadChannel; - c.members.add(interaction.member!.user.id); // TODO: When a thread is used, and a support role is added, automatically set channel permissions + c.members.add(interaction.member!.user.id); try { await c.send({ content: diff --git a/src/commands/help.ts b/src/commands/help.ts index e34500f..b6115f9 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -84,7 +84,12 @@ const callback = async (interaction: CommandInteraction): Promise => { if(currentPath[0] === "" || currentPath[0] === "help") { embed.setDescription( `Welcome to Nucleus\n\n` + - `Select a command to get started${(interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``}` // FIXME + `Select a command to get started${ + (interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? + `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : `` + }\n\n\n` + + `Nucleus is fully [open source](https://github.com/clicksminuteper/Nucleus), and all currently free features will remain free forever.\n\n` + + `You can invite Nucleus to your server using ${getCommandMentionByName("nucleus/invite")}` ) } else { const currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/')); @@ -95,7 +100,7 @@ const callback = async (interaction: CommandInteraction): Promise => { nameLocalized?: string; descriptionLocalized?: string; })[] = []; - //options + //o ptions if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") { const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup const Op2 = Op.options!.find(option => option.name === currentPath[2])! diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index b7c1405..407adf4 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -103,7 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = await m.awaitMessageComponent({ - filter: (i) => {return i.user.id === interaction.user.id && i.id === m.id}, + filter: (i) => {return i.user.id === interaction.user.id && i.channelId === interaction.channelId}, time: 300000 }); } catch { diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts index 8d5b709..f282e82 100644 --- a/src/commands/mod/slowmode.ts +++ b/src/commands/mod/slowmode.ts @@ -47,7 +47,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }) + "Are you sure you want to set the slowmode in this channel?" ) .setColor("Danger") - .setFailedMessage("No changes were made", "Danger", "CHANNEL.SLOWMODE.ON") + .setFailedMessage("No changes were made", "Success", "CHANNEL.SLOWMODE.ON") .send(); if (confirmation.cancelled || !confirmation.success) return; try { diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index cb6054d..8803e25 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -21,7 +21,6 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( "Nucleus is a bot that naturally needs to store data about servers.\n" + "We are entirely [open source](https://github.com/ClicksMinutePer/Nucleus), so you can check exactly what we store, and how it works.\n\n" + - "If you are a server administrator, you can view the options page in the dropdown under this message.\n\n" + // TODO "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)." ) .setEmoji("NUCLEUS.LOGO") @@ -51,7 +50,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( "**Facebook** - Facebook trackers include data such as your date of birth, and guess your age if not entered, your preferences, who you interact with and more.\n" + "**AMP** - AMP is a technology that allows websites to be served by Google. This means Google can store and track data, and are pushing this to as many pages as possible.\n\n" + - "Transcripts allow you to store all messages sent in a channel. This could be an issue in some cases, as they are hosted on [Pastebin](https://pastebin.com), so a leaked link could show all messages sent in the channel.\n" // TODO: Not on pastebin + "Transcripts allow you to store all messages sent in a channel. This is stored in our database along with the rest of the servers settings but is accessible by anyone with the link, so a leaked link could show all messages sent in the channel.\n" ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") @@ -62,26 +61,26 @@ const callback = async (interaction: CommandInteraction): Promise => { ].concat( (interaction.member as Discord.GuildMember).permissions.has("Administrator") ? [ - new Embed() - .setEmbed( - new EmojiEmbed() - .setTitle("Options") - .setDescription("Below are buttons for controlling this servers privacy settings") - .setEmoji("NUCLEUS.LOGO") - .setStatus("Danger") - ) - .setTitle("Options") - .setDescription("Options") - .setPageId(3) - .setComponents([ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Clear all data") - .setCustomId("clear-all-data") - .setStyle(ButtonStyle.Danger) - ]) - ]) - ] + new Embed() + .setEmbed( + new EmojiEmbed() + .setTitle("Options") + .setDescription("Below are buttons for controlling this servers privacy settings") + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ) + .setTitle("Options") + .setDescription("Options") + .setPageId(3) + .setComponents([ + new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setLabel("Clear all data") + .setCustomId("clear-all-data") + .setStyle(ButtonStyle.Danger) + ]) + ]) + ] : [] ); const m = await interaction.reply({ diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index d3d24c9..c3cac04 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -29,6 +29,7 @@ import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import listToAndMore from "../../utils/listToAndMore.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -37,14 +38,6 @@ const command = (builder: SlashCommandSubcommandBuilder) => const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id); -const listToAndMore = (list: string[], max: number) => { - // PineappleFan, Coded, Mini (and 10 more) - if(list.length > max) { - return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; - } - return list.join(", "); -} - const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise => { const back = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)); diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index 7259fa4..f2ec13a 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -31,7 +31,7 @@ const logs: Record = { const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("events").setDescription("Sets what events should be logged"); -const callback = async (interaction: CommandInteraction): Promise => { // TODO: Maybe merge this into /settings log general +const callback = async (interaction: CommandInteraction): Promise => { await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/general.ts similarity index 100% rename from src/commands/settings/logs/channel.ts rename to src/commands/settings/logs/general.ts diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/warnings.ts similarity index 100% rename from src/commands/settings/logs/staff.ts rename to src/commands/settings/logs/warnings.ts diff --git a/src/commands/settings/commands.ts b/src/commands/settings/moderation.ts similarity index 79% rename from src/commands/settings/commands.ts rename to src/commands/settings/moderation.ts index bbbb24a..ffd3063 100644 --- a/src/commands/settings/commands.ts +++ b/src/commands/settings/moderation.ts @@ -1,45 +1,23 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder, Message } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, Message, RoleSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; -import keyValueList from "../../utils/generateKeyValueList.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder - .setName("commands") + .setName("moderation") .setDescription("Links and text shown to a user after a moderator action is performed") - .addRoleOption((o) => o.setName("role").setDescription("The role given when a member is muted")); -const callback = async (interaction: CommandInteraction): Promise => { +const callback = async (interaction: CommandInteraction): Promise => { await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); let m: Message; - let clicked = ""; - if (interaction.options.get("role")) { - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Moderation Commands") - .setDescription( - keyValueList({ - role: `<@&${(interaction.options.get("role") as unknown as Role).id}>` - }) - ) - .setColor("Danger") - .send(true); - if (confirmation.cancelled) return - if (confirmation.success) { - await client.database.guilds.write(interaction.guild!.id, { - ["moderation.mute.role"]: (interaction.options.get("role") as unknown as Role).id - }); - } - } let timedOut = false; while (!timedOut) { const config = await client.database.guilds.read(interaction.guild!.id); @@ -52,8 +30,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStatus("Success") .setDescription( "These links are shown below the message sent in a user's DM when they are punished.\n\n" + - "**Mute Role:** " + - (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*") + "**Mute Role:** " + (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*") ) ], components: [ @@ -92,18 +69,17 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStyle(ButtonStyle.Secondary) ]), new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel(clicked === "clearMuteRole" ? "Click again to confirm" : "Clear mute role") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clearMuteRole") - .setStyle(ButtonStyle.Danger) - .setDisabled(!moderation.mute.role), new ButtonBuilder() .setCustomId("timeout") .setLabel("Mute timeout " + (moderation.mute.timeout ? "Enabled" : "Disabled")) .setStyle(moderation.mute.timeout ? ButtonStyle.Success : ButtonStyle.Danger) .setEmoji(getEmojiByName("CONTROL." + (moderation.mute.timeout ? "TICK" : "CROSS"), "id")) - ]) + ]), + new ActionRowBuilder().addComponents( + new RoleSelectMenuBuilder() + .setCustomId("muteRole") + .setPlaceholder("Select a new mute role") + ) ] }); let i; @@ -118,20 +94,13 @@ const callback = async (interaction: CommandInteraction): Promise => { } type modIDs = "mute" | "kick" | "ban" | "softban" | "warn" | "role"; let chosen = moderation[i.customId as modIDs]; - if ((i.component as ButtonComponent).customId === "clearMuteRole") { + if (i.isRoleSelectMenu()) { await i.deferUpdate(); - if (clicked === "clearMuteRole") { - await client.database.guilds.write(interaction.guild!.id, { - "moderation.mute.role": null - }); - } else { - clicked = "clearMuteRole"; - } + await client.database.guilds.write(interaction.guild!.id, { + "moderation.mute.role": i.values[0]! + }); continue; - } else { - clicked = ""; - } - if ((i.component as ButtonComponent).customId === "timeout") { + } else if ((i.component as ButtonComponent).customId === "timeout") { await i.deferUpdate(); await client.database.guilds.write(interaction.guild!.id, { "moderation.mute.timeout": !moderation.mute.timeout diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 635a1fd..02752c0 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -107,7 +107,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele new TextInputBuilder() .setLabel("Name") .setCustomId("name") - .setPlaceholder("Name here...") // TODO: Make better placeholder + .setPlaceholder("The name of the role (e.g. Programmer)") .setStyle(TextInputStyle.Short) .setValue(name ?? "") .setRequired(true) @@ -117,7 +117,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele new TextInputBuilder() .setLabel("Description") .setCustomId("description") - .setPlaceholder("Description here...") // TODO: Make better placeholder + .setPlaceholder("A short description of the role (e.g. A role for people who code)") .setStyle(TextInputStyle.Short) .setValue(description ?? "") ) diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 29e6ea4..3d718dc 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -1,66 +1,38 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; import Discord, { CommandInteraction, - GuildChannel, Message, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, StringSelectMenuBuilder, - Role, ButtonStyle, TextInputBuilder, ButtonComponent, ModalSubmitInteraction, - APIMessageComponentEmoji + APIMessageComponentEmoji, + RoleSelectMenuBuilder, + ChannelSelectMenuBuilder, + RoleSelectMenuInteraction, + ButtonInteraction, + ChannelSelectMenuInteraction, + TextInputStyle, + ModalBuilder, + ChannelType } from "discord.js"; import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js"; -import { ChannelType } from "discord-api-types/v9"; import client from "../../utils/client.js"; import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js"; import { capitalize } from "../../utils/generateKeyValueList.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; import type { GuildConfig } from "../../utils/database.js"; +import { LinkWarningFooter } from "../../utils/defaults.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("tickets") - .setDescription("Shows settings for tickets | Use no arguments to manage custom types") - .addStringOption((option) => - option - .setName("enabled") - .setDescription("If users should be able to create tickets") - .setRequired(false) - .addChoices( - {name: "Yes", value: "yes"}, - {name: "No",value: "no"} - ) - ) - .addChannelOption((option) => - option - .setName("category") - .setDescription("The category where tickets are created") - .addChannelTypes(ChannelType.GuildCategory) - .setRequired(false) - ) - .addNumberOption((option) => - option - .setName("maxticketsperuser") - .setDescription("The maximum amount of tickets a user can create | Default: 5") - .setRequired(false) - .setMinValue(1) - ) - .addRoleOption((option) => - option - .setName("supportrole") - .setDescription( - "This role will have view access to all tickets and will be pinged when a ticket is created" - ) - .setRequired(false) - ); + .setDescription("Shows settings for tickets") const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; @@ -69,215 +41,80 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true, fetchReply: true })) as Message; - const options = { - enabled: (interaction.options.get("enabled")?.value as string).startsWith("yes") as boolean | null, - category: interaction.options.get("category")?.channel as Discord.CategoryChannel | null, - maxtickets: interaction.options.get("maxticketsperuser")?.value as number | null, - supportping: interaction.options.get("supportrole")?.role as Role | null - }; - if (options.enabled !== null || options.category || options.maxtickets || options.supportping) { - if (options.category) { - let channel: GuildChannel | null; - try { - channel = await interaction.guild.channels.fetch(options.category.id) as GuildChannel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Tickets > Category") - .setDescription("The channel you provided is not a valid category") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.CategoryChannel; - if (channel.guild.id !== interaction.guild.id) - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets > Category") - .setDescription("You must choose a category in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - if (options.maxtickets) { - if (options.maxtickets < 1) - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets > Max Tickets") - .setDescription("You must choose a number greater than 0") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - let role: Role | null; - if (options.supportping) { - try { - role = await interaction.guild.roles.fetch(options.supportping.id); - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLE.DELETE") - .setTitle("Tickets > Support Ping") - .setDescription("The role you provided is not a valid role") - .setStatus("Danger") - ] - }); - } - if (!role) return; - role = role as Discord.Role; - if (role.guild.id !== interaction.guild.id) - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets > Support Ping") - .setDescription("You must choose a role in this server") - .setStatus("Danger") - .setEmoji("GUILD.ROLE.DELETE") - ] - }); - } - - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.TICKET.ARCHIVED") - .setTitle("Tickets") - .setDescription( - (options.category ? `**Category:** ${options.category.name}\n` : "") + - (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") + - (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") + - (options.enabled !== null - ? `**Enabled:** ${ - options.enabled - ? `${getEmojiByName("CONTROL.TICK")} Yes` - : `${getEmojiByName("CONTROL.CROSS")} No` - }\n` - : "") + - "\nAre you sure you want to apply these settings?" - ) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "GUILD.TICKET.OPEN") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - const toUpdate: Record = {}; - if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled; - if (options.category) toUpdate["tickets.category"] = options.category.id; - if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets; - if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id; - try { - await client.database.guilds.write(interaction.guild.id, toUpdate); - } catch (e) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets") - .setDescription("Something went wrong and the staff notifications channel could not be set") - .setStatus("Danger") - .setEmoji("GUILD.TICKET.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [] - }); - } - } const data = await client.database.guilds.read(interaction.guild.id); data.tickets.customTypes = (data.tickets.customTypes ?? []).filter( (value: string, index: number, array: string[]) => array.indexOf(value) === index ); - let lastClicked = ""; - const embed: EmojiEmbed = new EmojiEmbed(); - const compiledData = { - enabled: data.tickets.enabled, - category: data.tickets.category, - maxTickets: data.tickets.maxTickets, - supportRole: data.tickets.supportRole, - useCustom: data.tickets.useCustom, - types: data.tickets.types, - customTypes: data.tickets.customTypes as string[] | null - }; + let ticketData = (await client.database.guilds.read(interaction.guild.id)).tickets + let changesMade = false; let timedOut = false; + let errorMessage = ""; while (!timedOut) { - embed + const embed: EmojiEmbed = new EmojiEmbed() .setTitle("Tickets") .setDescription( - `${compiledData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${ - compiledData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No` + `${ticketData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${ + ticketData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No` }\n` + - `${compiledData.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${ - compiledData.category ? `<#${compiledData.category}>` : "*None set*" - }\n` + - `**Max Tickets:** ${compiledData.maxTickets ? compiledData.maxTickets : "*No limit*"}\n` + - `**Support Ping:** ${compiledData.supportRole ? `<@&${compiledData.supportRole}>` : "*None set*"}\n\n` + - (compiledData.useCustom && compiledData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") + - `${compiledData.useCustom ? "Custom" : "Default"} types in use` + + `${ticketData.category ? "" : getEmojiByName("TICKETS.REPORT")}` + + ((await interaction.guild.channels.fetch(ticketData.category!))!.type === ChannelType.GuildCategory ? + `**Category:** ` : `**Channel:** `) + // TODO: Notify if permissions are wrong + `${ticketData.category ? `<#${ticketData.category}>` : "*None set*"}\n` + + `**Max Tickets:** ${ticketData.maxTickets ? ticketData.maxTickets : "*No limit*"}\n` + + `**Support Ping:** ${ticketData.supportRole ? `<@&${ticketData.supportRole}>` : "*None set*"}\n\n` + + (ticketData.useCustom && ticketData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") + + `${ticketData.useCustom ? "Custom" : "Default"} types in use` + "\n\n" + `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*` ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN"); + if (errorMessage) embed.setFooter({text: errorMessage, iconURL: LinkWarningFooter.iconURL}); m = (await interaction.editReply({ embeds: [embed], components: [ - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents( new ButtonBuilder() - .setLabel("Tickets " + (compiledData.enabled ? "enabled" : "disabled")) - .setEmoji(getEmojiByName("CONTROL." + (compiledData.enabled ? "TICK" : "CROSS"), "id")) - .setStyle(compiledData.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setLabel("Tickets " + (ticketData.enabled ? "enabled" : "disabled")) + .setEmoji(getEmojiByName("CONTROL." + (ticketData.enabled ? "TICK" : "CROSS"), "id")) + .setStyle(ticketData.enabled ? ButtonStyle.Success : ButtonStyle.Danger) .setCustomId("enabled"), new ButtonBuilder() - .setLabel(lastClicked === "cat" ? "Click again to confirm" : "Clear category") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setCustomId("clearCategory") - .setDisabled(compiledData.category === null), - new ButtonBuilder() - .setLabel(lastClicked === "max" ? "Click again to confirm" : "Reset max tickets") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setCustomId("clearMaxTickets") - .setDisabled(compiledData.maxTickets === 5), - new ButtonBuilder() - .setLabel(lastClicked === "sup" ? "Click again to confirm" : "Clear support ping") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setCustomId("clearSupportPing") - .setDisabled(compiledData.supportRole === null) - ]), - new ActionRowBuilder().addComponents([ + .setLabel("Set max tickets") + .setEmoji(getEmojiByName("CONTROL.TICKET", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("setMaxTickets") + .setDisabled(!ticketData.enabled), new ButtonBuilder() .setLabel("Manage types") .setEmoji(getEmojiByName("TICKETS.OTHER", "id")) .setStyle(ButtonStyle.Secondary) - .setCustomId("manageTypes"), + .setCustomId("manageTypes") + .setDisabled(!ticketData.enabled), new ButtonBuilder() - .setLabel("Add create ticket button") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("send") - ]) + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id")) + .setStyle(ButtonStyle.Success) + .setCustomId("save") + .setDisabled(!changesMade) + ), + new ActionRowBuilder().addComponents( + new RoleSelectMenuBuilder() + .setCustomId("supportRole") + .setPlaceholder("Select a support role") + .setDisabled(!ticketData.enabled) + ), + new ActionRowBuilder().addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("category") + .setPlaceholder("Select a category or channel") + .setDisabled(!ticketData.enabled) + ) ] - })) as Message; - let i: MessageComponentInteraction; + })); + let i: RoleSelectMenuInteraction | ButtonInteraction | ChannelSelectMenuInteraction; try { - i = await m.awaitMessageComponent({ + i = await m.awaitMessageComponent<2 | 6 | 8>({ time: 300000, filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); @@ -285,176 +122,49 @@ const callback = async (interaction: CommandInteraction): Promise => { timedOut = true; continue; } - await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clearCategory") { - if (lastClicked === "cat") { - lastClicked = ""; - await client.database.guilds.write(interaction.guild.id, null, ["tickets.category"]); - compiledData.category = null; - } else lastClicked = "cat"; - } else if ((i.component as ButtonComponent).customId === "clearMaxTickets") { - if (lastClicked === "max") { - lastClicked = ""; - await client.database.guilds.write(interaction.guild.id, null, ["tickets.maxTickets"]); - compiledData.maxTickets = 5; - } else lastClicked = "max"; - } else if ((i.component as ButtonComponent).customId === "clearSupportPing") { - if (lastClicked === "sup") { - lastClicked = ""; - await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"]); - compiledData.supportRole = null; - } else lastClicked = "sup"; - } else if ((i.component as ButtonComponent).customId === "send") { - const ticketMessages = [ - { - label: "Create ticket", - description: "Click the button below to create a ticket" - }, - { - label: "Issues, questions or feedback?", - description: "Click below to open a ticket and get help from our staff team" - }, - { - label: "Contact Us", - description: "Click the button below to speak to us privately" - } - ]; - let innerTimedOut = false; - let templateSelected = false; - while (!innerTimedOut && !templateSelected) { - const enabled = compiledData.enabled && compiledData.category !== null; - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Ticket Button") - .setDescription("Select a message template to send in this channel") - .setFooter({ - text: enabled - ? "" - : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set." - }) - .setStatus(enabled ? "Success" : "Warning") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new StringSelectMenuBuilder() - .setOptions( - ticketMessages.map( - ( - t: { - label: string; - description: string; - value?: string; - }, - index - ) => { - t.value = index.toString(); - return t as { - value: string; - label: string; - description: string; - }; - } - ) - ) - .setCustomId("template") - .setMaxValues(1) - .setMinValues(1) - .setPlaceholder("Select a message template") - ]), - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("back") - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId("custom") - .setLabel("Custom") - .setEmoji(getEmojiByName("TICKETS.OTHER", "id")) - .setStyle(ButtonStyle.Primary) - ]) - ] - }); - let i: MessageComponentInteraction; - try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); - } catch (e) { - innerTimedOut = true; - continue; - } - if (i.isStringSelectMenu() && i.customId === "template") { + changesMade = true; + if (i.isRoleSelectMenu()) { + await i.deferUpdate(); + ticketData.supportRole = i.values[0] ?? null; + } else if (i.isChannelSelectMenu()) { + await i.deferUpdate(); + ticketData.category = i.values[0] ?? null; + } else { + switch(i.customId) { + case "save": { await i.deferUpdate(); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(ticketMessages[parseInt(i.values[0]!)]!.label) - .setDescription( - ticketMessages[parseInt(i.values[0]!)]!.description - ) - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Create Ticket") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("createticket") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "blank") { + await client.database.guilds.write(interaction.guild.id, { tickets: ticketData }); + changesMade = false; + break; + } + case "enabled": { await i.deferUpdate(); - await interaction.channel!.send({ - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Create Ticket") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("createticket") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "custom") { + ticketData.enabled = !ticketData.enabled; + break; + } + case "setMaxTickets": { await i.showModal( - new Discord.ModalBuilder() - .setCustomId("modal") - .setTitle("Enter embed details") + new ModalBuilder() + .setCustomId("maxTickets") + .setTitle("Set max tickets") .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("title") - .setLabel("Title") - .setMaxLength(256) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Short) - ), - new ActionRowBuilder().addComponents( + new ActionRowBuilder().setComponents( new TextInputBuilder() - .setCustomId("description") - .setLabel("Description") - .setMaxLength(4000) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Paragraph) + .setLabel("Max tickets - Leave blank for no limit") + .setCustomId("maxTickets") + .setPlaceholder("Enter a number") + .setRequired(false) + .setValue(ticketData.maxTickets.toString() ?? "") + .setMinLength(1) + .setMaxLength(3) + .setStyle(TextInputStyle.Short) ) ) - ); - await interaction.editReply({ + ) + await i.editReply({ embeds: [ new EmojiEmbed() - .setTitle("Ticket Button") + .setTitle("Tickets") .setDescription("Modal opened. If you can't see it, click back and try again.") .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") @@ -473,52 +183,34 @@ const callback = async (interaction: CommandInteraction): Promise => { try { out = await modalInteractionCollector( m, - (m) => m.channel!.id === interaction.channel!.id, - (m) => m.customId === "modify" + (m) => m.user.id === interaction.user.id, + (m) => m.customId === "back" ); } catch (e) { - innerTimedOut = true; continue; } + if (!out || out.isButton()) continue; out = out as ModalSubmitInteraction; - const title = out.fields.getTextInputValue("title"); - const description = out.fields.getTextInputValue("description"); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(title) - .setDescription(description) - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Create Ticket") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("createticket") - ]) - ] - }); - templateSelected = true; + let toAdd = out.fields.getTextInputValue("maxTickets"); + if(isNaN(parseInt(toAdd))) { + errorMessage = "You entered an invalid number - No changes were made"; + break; + } + ticketData.maxTickets = toAdd === "" ? 0 : parseInt(toAdd); + break; + } + case "manageTypes": { + await i.deferUpdate(); + ticketData = await manageTypes(interaction, data.tickets, m); + break; } } - } else if ((i.component as ButtonComponent).customId === "enabled") { - await client.database.guilds.write(interaction.guild.id, { - "tickets.enabled": !compiledData.enabled - }); - compiledData.enabled = !compiledData.enabled; - } else if ((i.component as ButtonComponent).customId === "manageTypes") { - data.tickets = await manageTypes(interaction, data.tickets, m as Message); } } - await interaction.editReply({ - embeds: [ embed.setFooter({ text: "Message timed out" })], - components: [] - }); }; + + async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) { let timedOut = false; let backPressed = false; @@ -543,7 +235,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") ], - components: (customTypes + components: (customTypes && customTypes.length > 0 ? [ new ActionRowBuilder().addComponents([ new Discord.StringSelectMenuBuilder() @@ -644,9 +336,6 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t if (i.isStringSelectMenu() && i.customId === "types") { await i.deferUpdate(); const types = toHexInteger(i.values, ticketTypes); - await client.database.guilds.write(interaction.guild!.id, { - "tickets.types": types - }); data.types = types; } else if (i.isStringSelectMenu() && i.customId === "removeTypes") { await i.deferUpdate(); @@ -655,9 +344,6 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t if (customTypes) { customTypes = customTypes.filter((t) => !types.includes(t)); customTypes = customTypes.length > 0 ? customTypes : null; - await client.database.guilds.write(interaction.guild!.id, { - "tickets.customTypes": customTypes - }); data.customTypes = customTypes; } } else if ((i.component as ButtonComponent).customId === "addType") { @@ -678,7 +364,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t ) ) ); - await interaction.editReply({ + await i.editReply({ embeds: [ new EmojiEmbed() .setTitle("Tickets > Types") @@ -700,8 +386,8 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t try { out = await modalInteractionCollector( m, - (m) => m.channel!.id === interaction.channel!.id, - (m) => m.customId === "addType" + (m) => m.user.id === interaction.user.id, + (m) => m.customId === "back" ); } catch (e) { continue; @@ -714,7 +400,8 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t } toAdd = toAdd.substring(0, 80); try { - await client.database.guilds.append(interaction.guild!.id, "tickets.customTypes", toAdd); + if(!data.customTypes) data.customTypes = []; + data.customTypes?.push(toAdd); } catch { continue; } diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 4e27d68..a353324 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -35,7 +35,7 @@ const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInter new TextInputBuilder() .setLabel("Name") .setCustomId("name") - .setPlaceholder("Name here...") // TODO: Make better placeholder + .setPlaceholder("The name of the track (e.g. Moderators)") .setStyle(TextInputStyle.Short) .setValue(name) .setRequired(true) diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 3467aee..1d695f8 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -174,211 +174,11 @@ const callback = async (interaction: CommandInteraction): Promise => { await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]); role = null; } - } else if ((i.component as ButtonComponent).customId === "send") { - const verifyMessages = [ - { - label: "Verify", - description: "Click the button below to get verified" - }, - { - label: "Get verified", - description: "To get access to the rest of the server, click the button below" - }, - { - label: "Ready to verify?", - description: "Click the button below to verify yourself" - } - ]; - let innerTimedOut = false; - let templateSelected = false; - while (!innerTimedOut && !templateSelected) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Button") - .setDescription("Select a message template to send in this channel") - .setFooter({ - text: role ? "" : "You do no have a verify role set so the button will not work." - }) - .setStatus(role ? "Success" : "Warning") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new StringSelectMenuBuilder() - .setOptions( - verifyMessages.map( - ( - t: { - label: string; - description: string; - value?: string; - }, - index - ) => { - t.value = index.toString(); - return t as { - value: string; - label: string; - description: string; - }; - } - ) - ) - .setCustomId("template") - .setMaxValues(1) - .setMinValues(1) - .setPlaceholder("Select a message template") - ]), - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("back") - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId("custom") - .setLabel("Custom") - .setEmoji(getEmojiByName("TICKETS.OTHER", "id")) - .setStyle(ButtonStyle.Primary) - ]) - ] - }); - let i: MessageComponentInteraction; - try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); - } catch (e) { - innerTimedOut = true; - continue; - } - if (i.isStringSelectMenu() && i.customId === "template") { - await i.deferUpdate(); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(verifyMessages[parseInt(i.values[0]!)]!.label) - .setDescription( - verifyMessages[parseInt(i.values[0]!)]!.description - ) - .setStatus("Success") - .setEmoji("CONTROL.BLOCKTICK") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Verify") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("verifybutton") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "blank") { - await i.deferUpdate(); - await interaction.channel!.send({ - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Verify") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("verifybutton") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "custom") { - await i.showModal( - new Discord.ModalBuilder() - .setCustomId("modal") - .setTitle("Enter embed details") - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("title") - .setLabel("Title") - .setMaxLength(256) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Short) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("description") - .setLabel("Description") - .setMaxLength(4000) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Paragraph) - ) - ) - ); - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Button") - .setDescription("Modal opened. If you can't see it, click back and try again.") - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("back") - ]) - ] - }); - let out; - try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === - interaction.channelId, - (m) => m.customId === "modify" - ); - } catch (e) { - innerTimedOut = true; - continue; - } - if (out !== null && out instanceof ModalSubmitInteraction) { - const title = out.fields.getTextInputValue("title"); - const description = out.fields.getTextInputValue("description"); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(title) - .setDescription(description) - .setStatus("Success") - .setEmoji("CONTROL.BLOCKTICK") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Verify") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("verifybutton") - ]) - ] - }); - templateSelected = true; - } - } - } } else { await i.deferUpdate(); break; } - } + } // TODO: On save, clear SEN await interaction.editReply({ embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message closed" })], components: [] diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts index c32bf8a..0eb8580 100644 --- a/src/commands/user/about.ts +++ b/src/commands/user/about.ts @@ -173,11 +173,8 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte generateKeyValueList({ member: renderUser(member.user), id: `\`${member.id}\``, - roles: `${member.roles.cache.size - 1}` // FIXME - }) + - "\n" + - (s.length > 0 ? s : "*None*") + - "\n" + roles: `${member.roles.cache.size - 1}` + }) + "\n" + (s.length > 0 ? s : "*None*") + "\n" ) ) .setTitle("Roles") diff --git a/src/commands/user/role.ts b/src/commands/user/role.ts index 19bd3c7..5aa8782 100644 --- a/src/commands/user/role.ts +++ b/src/commands/user/role.ts @@ -4,14 +4,7 @@ import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; - -const listToAndMore = (list: string[], max: number) => { - // PineappleFan, Coded, Mini (and 10 more) - if(list.length > max) { - return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; - } - return list.join(", "); -} +import listToAndMore from "../../utils/listToAndMore.js" const { renderUser } = client.logger; diff --git a/src/config/emojis.json b/src/config/emojis.json index 9ccb9fa..e4afdfb 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -98,9 +98,8 @@ "TITLEUPDATE": "729763053620691044", "TOPICUPDATE": "729763053477953536", "SLOWMODE": { - "ON": "777138171301068831", - "OFF": "777138171447869480", - "// TODO": "Make these green and red respectively" + "ON": "973616021304913950", + "OFF": "777138171447869480" }, "NSFW": { "ON": "729064531208175736", diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 8889f57..d25c9c4 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -43,7 +43,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after log(data); } else if ( (before.communicationDisabledUntilTimestamp ?? 0) < new Date().getTime() && - (after.communicationDisabledUntil ?? 0) > new Date().getTime() // TODO: test this + (after.communicationDisabledUntil ?? 0) > new Date().getTime() ) { await client.database.history.create( "mute", diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts index a728b79..09e062f 100644 --- a/src/events/roleUpdate.ts +++ b/src/events/roleUpdate.ts @@ -32,6 +32,12 @@ export async function callback(client: NucleusClient, oldRole: Role, newRole: Ro changes["mentionable"] = entry([oldRole.mentionable, newRole.mentionable], `${mentionable[0]} -> ${mentionable[1]}`); if (oldRole.hexColor !== newRole.hexColor) changes["color"] = entry([oldRole.hexColor, newRole.hexColor], `\`${oldRole.hexColor}\` -> \`${newRole.hexColor}\``); + if (oldRole.permissions.bitfield !== newRole.permissions.bitfield) { + changes["permissions"] = entry( + [oldRole.permissions.bitfield.toString(), newRole.permissions.bitfield.toString()], + `[[Old]](https://discordapi.com/permissions.html#${oldRole.permissions.bitfield.toString()}) -> [[New]](https://discordapi.com/permissions.html#${newRole.permissions.bitfield.toString()})` + ); + } if (Object.keys(changes).length === 4) return; @@ -48,6 +54,6 @@ export async function callback(client: NucleusClient, oldRole: Role, newRole: Ro hidden: { guild: newRole.guild.id } - }; // TODO: show perms changed (webpage) + }; // TODO: make our own page for this log(data); } diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 078587e..3c583f2 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -43,7 +43,7 @@ export default async function logAttachment(message: Message): Promise { "**General:** These are events like kicks and channel changes etc.\n" + `> These are standard logs and can be set with ${getCommandMentionByName("settings/logs/general")}\n` + "**Warnings:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member.\n" + - `> These may require special action by a moderator. You can set the channel with ${getCommandMentionByName("settings/logs/warnings")}\n` + // TODO + `> These may require special action by a moderator. You can set the channel with ${getCommandMentionByName("settings/logs/warnings")}\n` + "**Attachments:** All images sent in the server - Used to keep a record of deleted images\n" + `> Sent to a separate log channel to avoid spam. This can be set with ${getCommandMentionByName("settings/logs/attachments")}\n` + `> ${getEmojiByName("NUCLEUS.PREMIUM")} Please note this feature is only available with ${getCommandMentionByName("nucleus/premium")}` diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts index f7d0396..290e372 100644 --- a/src/reflex/verify.ts +++ b/src/reflex/verify.ts @@ -13,6 +13,8 @@ import fetch from "node-fetch"; import { TestString, NSFWCheck } from "./scanners.js"; import createPageIndicator from "../utils/createPageIndicator.js"; import client from "../utils/client.js"; +import singleNotify from "../utils/singleNotify.js"; +import { getCommandMentionByName } from "../utils/getCommandDataByName.js"; export interface VerifySchema { uID: string; @@ -206,7 +208,8 @@ export default async function (interaction: CommandInteraction | ButtonInteracti .setEmoji("CONTROL.BLOCKCROSS") ] }); - return; // TODO: SEN + singleNotify("verifyRoleDeleted", interaction.guild!.id, `The role given when a member is verified has been deleted. Use ${getCommandMentionByName("settings/verify")} to set a new one`, "Critical") + return; } verify[code] = { uID: interaction.member!.user.id, diff --git a/src/utils/database.ts b/src/utils/database.ts index 7deb3c1..6eb735e 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -168,7 +168,7 @@ export class ScanCache { } async write(hash: string, data: boolean, tags?: string[]) { - await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); // TODO: cleanup function maybe + await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); } async cleanup() { diff --git a/src/utils/listToAndMore.ts b/src/utils/listToAndMore.ts new file mode 100644 index 0000000..791ce40 --- /dev/null +++ b/src/utils/listToAndMore.ts @@ -0,0 +1,7 @@ +export default (list: string[], max: number) => { + // PineappleFan, Coded, Mini (and 10 more) + if(list.length > max) { + return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; + } + return list.join(", "); +} \ No newline at end of file From 01cba76c6dd7ad0b2d658ad84592d256f70cddec Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 18 Feb 2023 15:55:05 -0500 Subject: [PATCH 36/74] possibly finished current features --- src/commands/mod/about.ts | 8 +- src/commands/settings/automod.ts | 20 +- src/commands/settings/logs/attachment.ts | 242 ++++------- src/commands/settings/logs/events.ts | 184 +++++--- src/commands/settings/logs/general.ts | 197 --------- src/commands/settings/logs/warnings.ts | 226 +++------- src/commands/settings/moderation.ts | 14 +- src/commands/settings/rolemenu.ts | 9 +- src/commands/settings/stats.ts | 14 +- src/commands/settings/tickets.ts | 13 +- src/commands/settings/tracks.ts | 9 +- src/commands/settings/verify.ts | 210 +++------ src/commands/settings/welcome.ts | 498 ++++++++++------------ src/reflex/welcome.ts | 2 +- src/utils/calculate.ts | 8 +- src/utils/commandRegistration/register.ts | 2 +- src/utils/confirmationMessage.ts | 9 +- src/utils/dualCollector.ts | 21 +- 18 files changed, 592 insertions(+), 1094 deletions(-) delete mode 100644 src/commands/settings/logs/general.ts diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts index ab3ca49..0a9d962 100644 --- a/src/commands/mod/about.ts +++ b/src/commands/mod/about.ts @@ -3,7 +3,6 @@ import type { HistorySchema } from "../../utils/database.js"; import Discord, { CommandInteraction, GuildMember, - Interaction, Message, ActionRowBuilder, ButtonBuilder, @@ -401,12 +400,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, - (m) => m.customId === "modify" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { timedOut = true; continue; diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index c3cac04..f10cf67 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -8,11 +8,8 @@ import { ActionRowBuilder, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, CommandInteraction, - Interaction, Message, - MessageComponentInteraction, ModalBuilder, - ModalSubmitInteraction, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, @@ -317,12 +314,7 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu await i.showModal(modal); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { break; } @@ -612,12 +604,7 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, await i.showModal(modal); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { break; } @@ -912,7 +899,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } } - } while(!closed) + } while(!closed); + await interaction.deleteReply() }; diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 6f825bc..545e3ff 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -1,195 +1,105 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import { ChannelType } from "discord-api-types/v9"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; +import { getCommandMentionByName } from "../../../utils/getCommandDataByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("attachments") .setDescription("Where attachments should be logged to (Premium only)") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to log attachments in") - .addChannelTypes(ChannelType.GuildText) - .setRequired(false) - ); const callback = async (interaction: CommandInteraction): Promise => { - const m = (await interaction.reply({ + await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true - })) as Discord.Message; - if (interaction.options.get("channel")?.channel) { - let channel; - try { - channel = interaction.options.get("channel")?.channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Attachment Log Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild!.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") - .setTitle("Attachment Log Channel") + }) + + if(!client.database.premium.hasPremium(interaction.guild!.id)) return interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium Required") + .setDescription(`This feature is exclusive to ${getCommandMentionByName("nucleus/premium")} servers.`) + .setStatus("Danger") + .setEmoji("NUCLEUS.PREMIUM") + ] + }); + + let data = await client.database.guilds.read(interaction.guild!.id); + let channel = data.logging.staff.channel; + + let closed = false; + do { + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + ); + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("clear") + .setLabel("Clear") + .setStyle(ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(!channel), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(channel === data.logging.staff.channel) + ); + + const embed = new EmojiEmbed() + .setTitle("Attachments") .setDescription( - "This will be the channel all attachments will be sent to.\n\n" + - `Are you sure you want to set the attachment log channel to <#${channel.id}>?` + `The channel to send all attachments from the server, allowing you to check them if they are deleted` + + `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n` ) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild!.id, { - "logging.attachments.channel": channel.id - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "attachmentChannelUpdate", - displayName: "Attachment Log Channel Updated", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)) - }, - hidden: { - guild: interaction.guild!.id - } - }; - log(data); - } catch (e) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription("Something went wrong and the attachment log channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild!.id); - let channel = data.logging.staff.channel; + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE") - let timedOut = false; - while (!timedOut) { await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription( - channel - ? `Your attachment log channel is currently set to <#${channel}>` - : "This server does not have an attachment log channel" + - (await client.database.premium.hasPremium(interaction.guild!.id) - ? "" - : "\n\nThis server does not have premium, so this feature is disabled") - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset channel") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!channel) - ]) - ] + embeds: [embed], + components: [channelMenu, buttons] }); - let i; + + let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction; try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); + i = (await interaction.channel!.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { - timedOut = true; - continue; + closed = true; + break; } await i.deferUpdate(); - if ((i.component as unknown as ButtonInteraction).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild!.id, null, ["logging.announcements.channel"]); - channel = null; + if(i.isButton()) { + switch (i.customId) { + case "clear": { + channel = null; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, { + "logging.attachments.channel": channel + }); + data = await client.database.guilds.read(interaction.guild!.id); + break; + } } + } else { + channel = i.values[0]!; } - } - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription( - channel - ? `Your attachment log channel is currently set to <#${channel}>` - : "This server does not have an attachment log channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - .setFooter({ text: "Message closed" }) - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel("Clear") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true) - ]) - ] - }); + + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index f2ec13a..a1f24fa 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -1,9 +1,11 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder } from "discord.js"; -import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js"; -import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ChannelSelectMenuBuilder, ChannelType, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ButtonInteraction, StringSelectMenuInteraction, ChannelSelectMenuInteraction, APIMessageComponentEmoji } from "discord.js"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; +import compare from "lodash"; import { toHexArray, toHexInteger } from "../../../utils/calculate.js"; +import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; +import getEmojiByName from "../../../utils/getEmojiByName.js"; const logs: Record = { channelUpdate: "Channels created, deleted or modified", @@ -24,85 +26,135 @@ const logs: Record = { webhookUpdate: "Webhooks created or deleted", guildMemberVerify: "Member runs verify", autoModeratorDeleted: "Messages auto deleted by Nucleus", - nucleusSettingsUpdated: "Nucleus' settings updated by a moderator", - ticketUpdate: "Tickets created or deleted" + ticketUpdate: "Tickets created or deleted", + //nucleusSettingsUpdated: "Nucleus' settings updated by a moderator" // TODO }; const command = (builder: SlashCommandSubcommandBuilder) => - builder.setName("events").setDescription("Sets what events should be logged"); + builder + .setName("events") + .setDescription("The general log channel for the server, and setting what events to show") const callback = async (interaction: CommandInteraction): Promise => { - await interaction.reply({ + const m = (await interaction.reply({ embeds: LoadingEmbed, - fetchReply: true, - ephemeral: true - }); - let m: Message; - let timedOut = false; + ephemeral: true, + fetchReply: true + })) as Discord.Message; + + let config = await client.database.guilds.read(interaction.guild!.id); + let data = Object.assign({}, config.logging.logs); + let closed = false; + let show = false; do { - const config = await client.database.guilds.read(interaction.guild!.id); - const converted = toHexArray(config.logging.logs.toLog); - const selectPane = new StringSelectMenuBuilder() - .setPlaceholder("Set events to log") - .setMaxValues(Object.keys(logs).length) - .setCustomId("logs") - .setMinValues(0) - Object.keys(logs).map((e, i) => { - selectPane.addOptions(new StringSelectMenuOptionBuilder() - .setLabel(logs[e]!) - .setValue(i.toString()) - .setDefault(converted.includes(e)) + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + .setChannelTypes(ChannelType.GuildText) + ) + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Enabled" : "Disabled") + .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(getEmojiByName((data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS"), "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("remove") + .setLabel("Remove") + .setStyle(ButtonStyle.Danger) + .setDisabled(!data.channel), + new ButtonBuilder() + .setCustomId("show") + .setLabel("Manage Events") + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setDisabled(compare.isEqual(data, config.logging.logs)) + ) + + const converted = toHexArray(data.toLog); + const toLogMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setPlaceholder("Set events to log") + .setMaxValues(Object.keys(logs).length) + .setCustomId("logs") + .setMinValues(0) + ) + Object.keys(logs).map((e) => { + toLogMenu.components[0]!.addOptions( + new StringSelectMenuOptionBuilder() + .setLabel(logs[e]!) + .setValue(e) + .setDefault(converted.includes(e)) ) }); - m = (await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Logging Events") - .setDescription( - "Below are the events being logged in the server. You can toggle them on and off in the dropdown" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents(selectPane), - new ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("Select all").setStyle(ButtonStyle.Primary).setCustomId("all"), - new ButtonBuilder().setLabel("Select none").setStyle(ButtonStyle.Danger).setCustomId("none") - ]) - ] - })) as Message; - let i; + + const embed = new EmojiEmbed() + .setTitle("General Log Channel") + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE") + .setDescription( + `This is the channel that all events you set to be logged will be stored\n` + + `**Channel:** ${data.channel ? `<#${data.channel}>` : "None"}\n` + ) + + let components: ActionRowBuilder[] = [channelMenu, buttons]; + if(show) components.push(toLogMenu); + + await interaction.editReply({ + embeds: [embed], + components: components + }); + + let i: ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction; try { i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + }) as ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction; } catch (e) { - timedOut = true; - continue; + closed = true; + break; } + await i.deferUpdate(); - if (i.isStringSelectMenu() && i.customId === "logs") { - const selected = i.values; - const newLogs = toHexInteger(selected.map((e: string) => Object.keys(logs)[parseInt(e)]!)); - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.toLog": newLogs - }); - } else if (i.customId === "all") { - const newLogs = toHexInteger(Object.keys(logs).map((e) => e)); - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.toLog": newLogs - }); - } else if (i.customId === "none") { - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.toLog": 0 - }); + + if(i.isButton()) { + switch(i.customId) { + case "show": { + show = !show; + break; + } + case "switch": { + data.enabled = !data.enabled; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, {"logging.logs": data}); + config = await client.database.guilds.read(interaction.guild!.id); + data = Object.assign({}, config.logging.logs); + break; + } + case "remove": { + data.channel = null; + break; + } + } + } else if(i.isStringSelectMenu()) { + let hex = toHexInteger(i.values); + data.toLog = hex; + } else if(i.isChannelSelectMenu()) { + data.channel = i.values[0]!; } - } while (!timedOut); - await interaction.editReply({ embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })] }); - return; + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/logs/general.ts b/src/commands/settings/logs/general.ts deleted file mode 100644 index 9861d26..0000000 --- a/src/commands/settings/logs/general.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { LoadingEmbed } from "../../../utils/defaults.js"; -import { ChannelType } from "discord-api-types/v9"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction, ButtonComponent } from "discord.js"; -import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../../utils/confirmationMessage.js"; -import getEmojiByName from "../../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "discord.js"; -import client from "../../../utils/client.js"; - -const command = (builder: SlashCommandSubcommandBuilder) => - builder - .setName("general") - .setDescription("Sets or shows the log channel") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to set the log channel to") - .addChannelTypes(ChannelType.GuildText) - ); - -const callback = async (interaction: CommandInteraction): Promise => { - const m = (await interaction.reply({ - embeds: LoadingEmbed, - ephemeral: true, - fetchReply: true - })) as Discord.Message; - if (interaction.options.get("channel")?.channel) { - let channel; - try { - channel = interaction.options.get("channel")?.channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Log Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild!.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") - .setTitle("Log Channel") - .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.channel": channel.id - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "logChannelUpdate", - displayName: "Log Channel Changed", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)) - }, - hidden: { - guild: channel.guild.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log Channel") - .setDescription("Something went wrong and the log channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild!.id); - let channel = data.logging.logs.channel; - let timedOut = false; - while (!timedOut) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log channel") - .setDescription( - channel - ? `Your log channel is currently set to <#${channel}>` - : "This server does not have a log channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset channel") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!channel) - ]) - ] - }); - let i: ButtonInteraction; - try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }) as ButtonInteraction; - } catch (e) { - timedOut = true; - } - i = i! - await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild!.id, null, ["logging.logs.channel"]); - channel = null; - } - } - } - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log channel") - .setDescription( - channel - ? `Your log channel is currently set to <#${channel}>` - : "This server does not have a log channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - .setFooter({ text: "Message closed" }) - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel("Clear") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true) - ]) - ] - }); -}; - -const check = (interaction: CommandInteraction, _partial: boolean = false) => { - const member = interaction.member as Discord.GuildMember; - if (!member.permissions.has("ManageGuild")) - return "You must have the *Manage Server* permission to use this command"; - return true; -}; - -export { command }; -export { callback }; -export { check }; diff --git a/src/commands/settings/logs/warnings.ts b/src/commands/settings/logs/warnings.ts index 3855d34..6e5482c 100644 --- a/src/commands/settings/logs/warnings.ts +++ b/src/commands/settings/logs/warnings.ts @@ -1,8 +1,6 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import { ChannelType } from "discord-api-types/v9"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; @@ -11,182 +9,86 @@ const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("warnings") .setDescription("Settings for the staff notifications channel") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to set the staff notifications channel to") - .addChannelTypes(ChannelType.GuildText) - .setRequired(false) - ); const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; - const m = (await interaction.reply({ + await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true - })) as Discord.Message; - if (interaction.options.get("channel")?.channel) { - let channel; - try { - channel = interaction.options.get("channel")?.channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Staff Notifications Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") + }) + + let data = await client.database.guilds.read(interaction.guild.id); + let channel = data.logging.staff.channel; + let closed = false; + do { + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + ); + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("clear") + .setLabel("Clear") + .setStyle(ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(!channel), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(channel === data.logging.staff.channel) + ); + + const embed = new EmojiEmbed() .setTitle("Staff Notifications Channel") + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE") .setDescription( - "This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n" + - `Are you sure you want to set the staff notifications channel to <#${channel.id}>?` + `Logs which require an action from a moderator or administrator will be sent to this channel.\n` + + `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n` ) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild.id, { - "logging.staff.channel": channel.id - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "staffChannelUpdate", - displayName: "Staff Notifications Channel Updated", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)) - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } catch (e) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications Channel") - .setDescription("Something went wrong and the staff notifications channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild.id); - let channel = data.logging.staff.channel; - let timedOut = false; - while (!timedOut) { + await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications channel") - .setDescription( - channel - ? `Your staff notifications channel is currently set to <#${channel}>` - : "This server does not have a staff notifications channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset channel") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!channel) - ]) - ] + embeds: [embed], + components: [channelMenu, buttons] }); - let i; + + let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction; try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); + i = (await interaction.channel!.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { - timedOut = true; - continue; + closed = true; + break; } await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild.id, null, ["logging.staff.channel"]); - channel = null; + if(i.isButton()) { + switch (i.customId) { + case "clear": { + channel = null; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, { + "logging.warnings.channel": channel + }); + data = await client.database.guilds.read(interaction.guild!.id); + break; + } } + } else { + channel = i.values[0]!; } - } - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications channel") - .setDescription( - channel - ? `Your staff notifications channel is currently set to <#${channel}>` - : "This server does not have a staff notifications channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - .setFooter({ text: "Message closed" }) - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel("Clear") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true) - ]) - ] - }); + } while (!closed); + + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/moderation.ts b/src/commands/settings/moderation.ts index ffd3063..c7f0dd0 100644 --- a/src/commands/settings/moderation.ts +++ b/src/commands/settings/moderation.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, Message, RoleSelectMenuBuilder } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, RoleSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; @@ -12,17 +12,16 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setDescription("Links and text shown to a user after a moderator action is performed") const callback = async (interaction: CommandInteraction): Promise => { - await interaction.reply({ + const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); - let m: Message; let timedOut = false; while (!timedOut) { const config = await client.database.guilds.read(interaction.guild!.id); const moderation = config.moderation; - m = await interaction.editReply({ + await interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Moderation Commands") @@ -152,11 +151,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let out: Discord.ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { continue; } @@ -175,6 +170,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } } + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 02752c0..cccb6f6 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -144,11 +144,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele let out: Discord.ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { console.error(e); out = null; @@ -472,7 +468,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } } - } while (!closed) + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f8a57b7..d46b57e 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -74,7 +74,6 @@ const showModal = async (interaction: MessageComponentInteraction, current: { en type ObjectSchema = Record - const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise => { let closed = false; let cancelled = false; @@ -172,11 +171,7 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr }); showModal(i, {name: newChannelName, enabled: newChannelEnabled}) - const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id && m.user!.id === interaction.user!.id, - (i) => i.channel!.id === interaction.channel!.id && i.user!.id === interaction.user!.id && i.message!.id === m.id - ); + const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector(m, interaction.user); if (!out) continue; if (out.isButton()) continue; newChannelName = out.fields.getTextInputValue("text"); @@ -340,11 +335,7 @@ const callback = async (interaction: CommandInteraction) => { }); let out: Discord.ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { continue; } @@ -396,6 +387,7 @@ const callback = async (interaction: CommandInteraction) => { } } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 3d718dc..af74475 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -181,11 +181,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let out; try { - out = await modalInteractionCollector( - m, - (m) => m.user.id === interaction.user.id, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { continue; } @@ -207,6 +203,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } } + await interaction.deleteReply() }; @@ -384,11 +381,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t }); let out; try { - out = await modalInteractionCollector( - m, - (m) => m.user.id === interaction.user.id, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { continue; } diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index a353324..612d069 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -63,11 +63,7 @@ const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInter let out: ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null; } catch (e) { console.error(e); out = null; @@ -440,7 +436,8 @@ const callback = async (interaction: CommandInteraction) => { } } - } while (!closed) + } while (!closed); + await interaction.deleteReply() } const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 1d695f8..920beb4 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -1,33 +1,25 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, - Interaction, Message, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, - ModalSubmitInteraction, - Role, ButtonStyle, - StringSelectMenuBuilder, - TextInputBuilder, - EmbedBuilder, - ButtonComponent + RoleSelectMenuBuilder, + APIMessageComponentEmoji } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; -import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import { getCommandMentionByName } from "../../utils/getCommandDataByName.js"; + const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("verify") - .setDescription("Manage the role given after typing /verify") - .addRoleOption((option) => - option.setName("role").setDescription("The role to give after verifying").setRequired(false) - ); + .setDescription("Manage the role given after a user runs /verify") + const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; @@ -36,153 +28,79 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true, fetchReply: true })) as Message; - if (interaction.options.get("role")?.role) { - let role: Role; - try { - role = interaction.options.get("role")?.role as Role; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Verify Role") - .setDescription("The role you provided is not a valid role") - .setStatus("Danger") - ] - }); - } - role = role as Discord.Role; - if (role.guild.id !== interaction.guild.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription("You must choose a role in this server") - .setStatus("Danger") - .setEmoji("GUILD.ROLES.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.EDIT") + + let closed = false; + let config = await client.database.guilds.read(interaction.guild.id); + let data = Object.assign({}, config.verify); + do { + console.log(config.verify, data) + const selectMenu = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("role") + .setPlaceholder("Select a role") + ); + + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Disabled" : "Enabled") + .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success) + .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setDisabled(data.role === config.verify.role && data.enabled === config.verify.enabled) + ); + + const embed = new EmojiEmbed() .setTitle("Verify Role") - .setDescription(`Are you sure you want to set the verify role to <@&${role.id}>?`) - .setColor("Warning") - .setFailedMessage("No changes were made", "Warning", "GUILD.ROLES.DELETE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild.id, { - "verify.role": role.id, - "verify.enabled": true - }); - const { log, NucleusColors, entry, renderUser, renderRole } = client.logger; - const data = { - meta: { - type: "verifyRoleChanged", - displayName: "Verify Role Changed", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.green, - emoji: "CONTROL.BLOCKTICK", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - role: entry(role.id, renderRole(role)) - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription("Something went wrong while setting the verify role") - .setStatus("Danger") - .setEmoji("GUILD.ROLES.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild.id); - let role = data.verify.role; + .setDescription( + `Select a role to be given to users after they run ${getCommandMentionByName("verify")}` + + `\n\nCurrent role: ${config.verify.role ? `<@&${config.verify.role}>` : "None"}` + ) + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE"); - let timedOut = false; - while (!timedOut) { await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription( - role ? `Your verify role is currently set to <@&${role}>` : "You have not set a verify role" - ) - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset role") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!role), - new ButtonBuilder() - .setCustomId("send") - .setLabel("Add verify button") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Primary) - ]) - ] + embeds: [embed], + components: [selectMenu, buttons] }); - let i: MessageComponentInteraction; + + let i; try { i = await m.awaitMessageComponent({ time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } + filter: (i) => { return i.user.id === interaction.user.id } }); } catch (e) { - timedOut = true; + closed = true; continue; } + await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]); - role = null; + + if(i.isButton()) { + switch (i.customId) { + case "save": { + client.database.guilds.write(interaction.guild.id, {"verify": data} ) + config = await client.database.guilds.read(interaction.guild.id); + break + } + case "switch": { + data.enabled = !data.enabled; + break + } } } else { - await i.deferUpdate(); - break; + data.role = i.values[0]!; } - } // TODO: On save, clear SEN - await interaction.editReply({ - embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message closed" })], - components: [] - }); + + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index b9c1b9f..ae55fc0 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -1,304 +1,260 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { - Channel, CommandInteraction, - Message, + AutocompleteInteraction, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, - Role, ButtonStyle, - AutocompleteInteraction, - GuildChannel, - EmbedBuilder + APIMessageComponentEmoji, + ChannelSelectMenuBuilder, + RoleSelectMenuBuilder, + RoleSelectMenuInteraction, + ChannelSelectMenuInteraction, + ButtonInteraction, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ModalSubmitInteraction, } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; -import generateKeyValueList from "../../utils/generateKeyValueList.js"; -import { ChannelType } from "discord-api-types/v9"; import getEmojiByName from "../../utils/getEmojiByName.js"; +import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("welcome") .setDescription("Messages and roles sent or given when someone joins the server") - .addStringOption((option) => - option - .setName("message") - .setDescription("The message to send when someone joins the server") - .setAutocomplete(true) - ) - .addRoleOption((option) => - option.setName("role").setDescription("The role given when someone joins the server") - ) - .addRoleOption((option) => - option.setName("ping").setDescription("The role pinged when someone joins the server") - ) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel the welcome message should be sent to") - .addChannelTypes(ChannelType.GuildText) - ); -const callback = async (interaction: CommandInteraction): Promise => { - const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger; - await interaction.reply({ +const callback = async (interaction: CommandInteraction): Promise => { + const { renderChannel } = client.logger; + const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true }); - let m: Message; - if ( - interaction.options.get("role")?.role || - interaction.options.get("channel")?.channel || - interaction.options.get("message")?.value as string - ) { - let role: Role | null; - let ping: Role | null; - let channel: Channel | null; - const message: string | null = interaction.options.get("message")?.value as string | null; - try { - role = interaction.options.get("role")?.role as Role | null; - ping = interaction.options.get("ping")?.role as Role | null; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Welcome Events") - .setDescription("The role you provided is not a valid role") - .setStatus("Danger") - ] - }); - } - try { - channel = interaction.options.get("channel")?.channel as Channel | null; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Welcome Events") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - const options: { - role?: string; - ping?: string; - channel?: string; - message?: string; - } = {}; - - if (role) options.role = renderRole(role); - if (ping) options.ping = renderRole(ping); - if (channel) options.channel = renderChannel(channel as GuildChannel); - if (message) options.message = "\n> " + message; - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.EDIT") - .setTitle("Welcome Events") - .setDescription(generateKeyValueList(options)) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "GUILD.ROLES.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - const toChange: { - "welcome.role"?: string; - "welcome.ping"?: string; - "welcome.channel"?: string; - "welcome.message"?: string; - } = {}; - if (role) toChange["welcome.role"] = role.id; - if (ping) toChange["welcome.ping"] = ping.id; - if (channel) toChange["welcome.channel"] = channel.id; - if (message) toChange["welcome.message"] = message; - await client.database.guilds.write(interaction.guild!.id, toChange); - const list: { - memberId: ReturnType; - changedBy: ReturnType; - role?: ReturnType; - ping?: ReturnType; - channel?: ReturnType; - message?: ReturnType; - } = { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)) - }; - if (role) list.role = entry(role.id, renderRole(role)); - if (ping) list.ping = entry(ping.id, renderRole(ping)); - if (channel) list.channel = entry(channel.id, renderChannel(channel as GuildChannel)); - if (message) list.message = entry(message, `\`${message}\``); - const data = { - meta: { - type: "welcomeSettingsUpdated", - displayName: "Welcome Settings Changed", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.green, - emoji: "CONTROL.BLOCKTICK", - timestamp: new Date().getTime() - }, - list: list, - hidden: { - guild: interaction.guild!.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Welcome Events") - .setDescription("Something went wrong while updating welcome settings") - .setStatus("Danger") - .setEmoji("GUILD.ROLES.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Welcome Events") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [] - }); - } - } - let lastClicked = null; - let timedOut = false; + let closed = false; + let config = await client.database.guilds.read(interaction.guild!.id); + let data = Object.assign({}, config.welcome); do { - const config = await client.database.guilds.read(interaction.guild!.id); - m = (await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Welcome Events") - .setDescription( - `**Message:** ${config.welcome.message ? `\n> ${config.welcome.message}` : "*None set*"}\n` + - `**Role:** ${ - config.welcome.role - ? renderRole((await interaction.guild!.roles.fetch(config.welcome.role))!) - : "*None set*" - }\n` + - `**Ping:** ${ - config.welcome.ping - ? renderRole((await interaction.guild!.roles.fetch(config.welcome.ping))!) - : "*None set*" - }\n` + - `**Channel:** ${ - config.welcome.channel - ? config.welcome.channel === "dm" - ? "DM" - : renderChannel((await interaction.guild!.channels.fetch(config.welcome.channel))!) - : "*None set*" - }` + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Enabled" : "Disabled") + .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("message") + .setLabel((data.message ? "Change" : "Set") + "Message") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("channelDM") + .setLabel("Send in DMs") + .setStyle(ButtonStyle.Primary) + .setDisabled(data.channel === "dm"), + new ButtonBuilder() + .setCustomId("role") + .setLabel("Clear Role") + .setStyle(ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setDisabled( + data.enabled === config.welcome.enabled && + data.message === config.welcome.message && + data.role === config.welcome.role && + data.ping === config.welcome.ping && + data.channel === config.welcome.channel ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel(lastClicked === "clear-message" ? "Click again to confirm" : "Clear Message") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-message") - .setDisabled(!config.welcome.message) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel(lastClicked === "clear-role" ? "Click again to confirm" : "Clear Role") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-role") - .setDisabled(!config.welcome.role) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel(lastClicked === "clear-ping" ? "Click again to confirm" : "Clear Ping") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-ping") - .setDisabled(!config.welcome.ping) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel(lastClicked === "clear-channel" ? "Click again to confirm" : "Clear Channel") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-channel") - .setDisabled(!config.welcome.channel) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel("Set Channel to DM") - .setCustomId("set-channel-dm") - .setDisabled(config.welcome.channel === "dm") - .setStyle(ButtonStyle.Secondary) - ]) - ] - })) as Message; - let i: MessageComponentInteraction; + ); + + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel to send welcome messages to") + ); + const roleMenu = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("roleToGive") + .setPlaceholder("Select a role to give to the member when they join the server") + ); + const pingMenu = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("roleToPing") + .setPlaceholder("Select a role to ping when a member joins the server") + ); + + const embed = new EmojiEmbed() + .setTitle("Welcome Settings") + .setStatus("Success") + .setDescription( + `${getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Welcome messages and roles are ${data.enabled ? "enabled" : "disabled"}\n` + + `**Welcome message:** ${data.message ? + `\n> ` + + await convertCurlyBracketString( + data.message, + interaction.user.id, + interaction.user.username, + interaction.guild!.name, + interaction.guild!.members + ) + : "*None*"}\n` + + `**Send message in:** ` + (data.channel ? (data.channel == "dm" ? "DMs" : renderChannel(data.channel)) : `*None set*`) + `\n` + + `**Role to ping:** ` + (data.ping ? `<@&${data.ping}>` : `*None set*`) + `\n` + + `**Role given on join:** ` + (data.role ? `<@&${data.role}>` : `*None set*`) + ) + + await interaction.editReply({ + embeds: [embed], + components: [buttons, channelMenu, roleMenu, pingMenu] + }); + + let i: RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction; try { i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); + filter: (interaction) => interaction.user.id === interaction.user.id, + time: 300000 + }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction; } catch (e) { - timedOut = true; + closed = true; continue; } - await i.deferUpdate(); - if (i.customId === "clear-message") { - if (lastClicked === "clear-message") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.message": null - }); - lastClicked = null; - } else { - lastClicked = "clear-message"; - } - } else if (i.customId === "clear-role") { - if (lastClicked === "clear-role") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.role": null - }); - lastClicked = null; - } else { - lastClicked = "clear-role"; - } - } else if (i.customId === "clear-ping") { - if (lastClicked === "clear-ping") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.ping": null - }); - lastClicked = null; - } else { - lastClicked = "clear-ping"; + + if(i.isButton()) { + switch(i.customId) { + case "switch": { + await i.deferUpdate(); + data.enabled = !data.enabled; + break; + } + case "message": { + const modal = new ModalBuilder() + .setCustomId("modal") + .setTitle("Welcome Message") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex1") + .setLabel("Server Info (1/3)") + .setPlaceholder( + `{serverName} - This server's name\n\n` + + `These placeholders will be replaced with the server's name, etc..` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex2") + .setLabel("Member Counts (2/3) - {MemberCount:...}") + .setPlaceholder( + `{:all} - Total member count\n` + + `{:humans} - Total non-bot users\n` + + `{:bots} - Number of bots\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex3") + .setLabel("Member who joined (3/3) - {member:...}") + .setPlaceholder( + `{:name} - The members name\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(TextInputStyle.Paragraph) + ), + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("message") + .setPlaceholder("Enter a message to send when someone joins the server") + .setValue(data.message ?? "") + .setLabel("Message") + .setStyle(TextInputStyle.Paragraph) + ) + ) + const button = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + await i.showModal(modal) + await i.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Welcome Settings") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + ], + components: [button] + }); + + let out: ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) break; + data.message = out.fields.getTextInputValue("message") ?? null; + break; + } + case "save": { + await i.deferUpdate(); + await client.database.guilds.write(interaction.guild!.id, {"welcome": data}); + config = await client.database.guilds.read(interaction.guild!.id); + data = Object.assign({}, config.welcome); + break; + } + case "channelDM": { + await i.deferUpdate(); + data.channel = "dm"; + break; + } + case "role": { + await i.deferUpdate(); + data.role = null; + break; + } } - } else if (i.customId === "clear-channel") { - if (lastClicked === "clear-channel") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.channel": null - }); - lastClicked = null; - } else { - lastClicked = "clear-channel"; + } else if (i.isRoleSelectMenu()) { + await i.deferUpdate(); + switch(i.customId) { + case "roleToGive": { + data.role = i.values[0]!; + break + } + case "roleToPing": { + data.ping = i.values[0]!; + break + } } - } else if (i.customId === "set-channel-dm") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.channel": "dm" - }); - lastClicked = null; + } else { + await i.deferUpdate(); + data.channel = i.values[0]!; } - } while (!timedOut); - await interaction.editReply({ - embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })], - components: [] - }); + + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { @@ -341,4 +297,4 @@ const autocomplete = async (interaction: AutocompleteInteraction): Promise` : "") + `<@${member.id}>` + content: (config.welcome.ping ? `<@&${config.welcome.ping}>` : "") + `<@${member.id}>` }); } catch (err) { singleNotify( diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts index 0bd5a9f..fde1340 100644 --- a/src/utils/calculate.ts +++ b/src/utils/calculate.ts @@ -17,16 +17,14 @@ const logs = [ "webhookUpdate", "guildMemberVerify", "autoModeratorDeleted", - "nucleusSettingsUpdated", - "ticketUpdate" + "ticketUpdate", + // "nucleusSettingsUpdated" ]; const tickets = ["support", "report", "question", "issue", "suggestion", "other"]; const toHexInteger = (permissions: string[], array?: string[]): string => { - if (!array) { - array = logs; - } + if (!array) { array = logs; } let int = 0n; for (const perm of permissions) { diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 50c8e78..6805019 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -56,7 +56,7 @@ async function registerCommands() { } } - console.log(`${colours.green}Processed ${processed.length} commands`) + console.log(`${colours.green}Processed ${processed.length} commands${colours.none}`) return processed; }; diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 43a0c8f..f7cccaf 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -1,11 +1,9 @@ import { TextInputBuilder } from "discord.js"; import Discord, { CommandInteraction, - Interaction, Message, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, ModalSubmitInteraction, ButtonStyle, TextInputStyle @@ -246,12 +244,7 @@ class confirmationMessage { }); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === this.interaction.channelId, - (m) => m.customId === "reason" - ); + out = await modalInteractionCollector(m, this.interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { cancelled = true; continue; diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index f55716e..072f73f 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, Client, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; +import { ButtonInteraction, Client, User, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; import client from "./client.js"; export default async function ( @@ -45,17 +45,25 @@ export default async function ( return out; } +function defaultInteractionFilter(i: MessageComponentInteraction, user: User, m: Message) { + return i.channel!.id === m.channel!.id && i.user.id === user.id +} +function defaultModalFilter(i: ModalSubmitInteraction, user: User, m: Message) { + return i.channel!.id === m.channel!.id && i.user.id === user.id +} + + export async function modalInteractionCollector( - m: Message, - modalFilter: (i: Interaction) => boolean | Promise, - interactionFilter: (i: MessageComponentInteraction) => boolean | Promise + m: Message, user: User, + modalFilter?: (i: Interaction) => boolean | Promise, + interactionFilter?: (i: MessageComponentInteraction) => boolean | Promise ): Promise { let out: ButtonInteraction | ModalSubmitInteraction; try { out = await new Promise((resolve, _reject) => { const int = m .createMessageComponentCollector({ - filter: (i: MessageComponentInteraction) => interactionFilter(i), + filter: (i: MessageComponentInteraction) => (interactionFilter ? interactionFilter(i) : true) && defaultInteractionFilter(i, user, m), time: 300000 }) .on("collect", async (i: ButtonInteraction) => { @@ -65,7 +73,7 @@ export async function modalInteractionCollector( resolve(i); }); const mod = new InteractionCollector(client as Client, { - filter: (i: Interaction) => modalFilter(i) && i.isModalSubmit(), + filter: (i: Interaction) => (modalFilter ? modalFilter(i) : true) && i.isModalSubmit() && defaultModalFilter(i, user, m), time: 300000 }).on("collect", async (i: ModalSubmitInteraction) => { int.stop(); @@ -75,6 +83,7 @@ export async function modalInteractionCollector( }); }); } catch (e) { + console.log(e); return null; } return out; From baee2c19da257be597839dbc7fc353123d841210 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 18 Feb 2023 16:11:06 -0500 Subject: [PATCH 37/74] added autopublish --- src/commands/settings/autopublish.ts | 96 ++++++++++++++++++++++++++++ src/config/default.json | 6 +- src/events/messageCreate.ts | 5 ++ src/utils/database.ts | 4 ++ src/utils/memory.ts | 4 +- 5 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/commands/settings/autopublish.ts diff --git a/src/commands/settings/autopublish.ts b/src/commands/settings/autopublish.ts new file mode 100644 index 0000000..c4b9b56 --- /dev/null +++ b/src/commands/settings/autopublish.ts @@ -0,0 +1,96 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js"; +import type Discord from "discord.js"; +import client from "../../utils/client.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import compare from "lodash" +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; + +export const command = new SlashCommandSubcommandBuilder() + .setName("autopublish") + .setDescription("Automatically publish messages posted in announcement channels"); + +export const callback = async (interaction: CommandInteraction): Promise => { + await interaction.reply({ + embeds: LoadingEmbed, + ephemeral: true, + fetchReply: true + }); + + let closed = false; + let config = await client.database.guilds.read(interaction.guild!.id); + let data = Object.assign({}, config.autoPublish); + do { + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Disabled" : "Enabled") + .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success) + .setEmoji(data.enabled ? "✅" : "❌"), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji("💾") + .setDisabled(compare.isEqual(data, config.autoPublish)) + ); + + const channelSelect = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + .setMinValues(1) + ); + + const embed = new EmojiEmbed() + + await interaction.editReply({ + embeds: [embed], + components: [channelSelect, buttons] + }); + + let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction; + try { + i = await interaction.channel!.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + + if(i.isButton()) { + switch(i.customId) { + case "switch": { + data.enabled = !data.enabled; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, { "autoPublish": data }); + config = await client.database.guilds.read(interaction.guild!.id); + data = Object.assign({}, config.autoPublish); + break; + } + } + } else { + for(const channel of i.values) { + data.channels.includes(channel) ? data.channels.splice(data.channels.indexOf(channel), 1) : data.channels.push(channel); + } + } + + } while (!closed); + + await interaction.deleteReply(); +} + +export const check = (interaction: CommandInteraction, _partial: boolean = false) => { + const member = interaction.member as Discord.GuildMember; + const me = interaction.guild!.members.me as Discord.GuildMember; + if (!member.permissions.has("ManageMessages")) + return "You must have the *Manage Messages* permission to use this command"; + if (_partial) return true; + if (!me.permissions.has("ManageMessages")) return "I do not have the *Manage Messages* permission"; + return true; +}; diff --git a/src/config/default.json b/src/config/default.json index e972e93..858a835 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -114,5 +114,9 @@ }, "tracks": [], "roleMenu": [], - "tags": {} + "tags": {}, + "autoPublish": { + "enabled": false, + "channels": [] + } } diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 804e6ea..0a62019 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -31,6 +31,11 @@ export async function callback(_client: NucleusClient, message: Message) { let userAllow = config.filters.clean.allowed.user.includes(message.author.id); if(!roleAllow && !userAllow) return await message.delete(); } + + if (config.autoPublish.enabled && config.autoPublish.channels.includes(message.channel.id)) { + await message.crosspost(); + } + const filter = getEmojiByName("ICONS.FILTER"); let attachmentJump = ""; if (config.logging.attachments.saved[message.channel.id + message.id]) { diff --git a/src/utils/database.ts b/src/utils/database.ts index 6eb735e..aa7fd36 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -340,6 +340,10 @@ export interface GuildConfig { } } }; + autoPublish: { + enabled: boolean; + channels: string[]; + } welcome: { enabled: boolean; role: string | null; diff --git a/src/utils/memory.ts b/src/utils/memory.ts index 870ffaf..60a6535 100644 --- a/src/utils/memory.ts +++ b/src/utils/memory.ts @@ -7,6 +7,7 @@ interface GuildData { logging: GuildConfig["logging"]; tickets: GuildConfig["tickets"]; tags: GuildConfig["tags"]; + autoPublish: GuildConfig["autoPublish"]; } class Memory { @@ -31,7 +32,8 @@ class Memory { filters: guildData.filters, logging: guildData.logging, tickets: guildData.tickets, - tags: guildData.tags + tags: guildData.tags, + autoPublish: guildData.autoPublish }); } return this.memory.get(guild)!; From 46518a4bb599bcdeb4c2b92f88e9de093c9dc5ea Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 18 Feb 2023 17:08:23 -0500 Subject: [PATCH 38/74] finished? --- src/commands/server/buttons.ts | 252 ++++++++++++++++++ src/config/emojis.json | 18 +- src/events/interactionCreate.ts | 4 +- src/utils/commandRegistration/register.ts | 40 +-- .../slashCommandBuilder.ts | 6 +- src/utils/createPageIndicator.ts | 4 +- 6 files changed, 293 insertions(+), 31 deletions(-) create mode 100644 src/commands/server/buttons.ts diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts new file mode 100644 index 0000000..4a299b2 --- /dev/null +++ b/src/commands/server/buttons.ts @@ -0,0 +1,252 @@ +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, CommandInteraction, MessageCreateOptions, ModalBuilder, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; +import type Discord from "discord.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import lodash from "lodash"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; + +export const command = new SlashCommandSubcommandBuilder() + .setName("buttons") + .setDescription("Create clickable buttons for verifying, role menus etc."); + +interface Data { + buttons: string[], + title: string | null, + description: string | null, + color: number, + channel: string | null +} + +const colors: Record = { + RED: 0xF27878, + ORANGE: 0xE5AB71, + YELLOW: 0xF2D478, + GREEN: 0x65CC76, + BLUE: 0x72AEF5, + PURPLE: 0xA358B2, + PINK: 0xD46899, + GRAY: 0x999999, +} + +const buttonNames: Record = { + verifybutton: "Verify", + rolemenu: "Role Menu", + createticket: "Ticket" +} + +export const callback = async (interaction: CommandInteraction): Promise => { + + const m = await interaction.reply({ + embeds: LoadingEmbed, + fetchReply: true, + ephemeral: true + }); + + let closed = false; + let data: Data = { + buttons: [], + title: null, + description: null, + color: colors["RED"]!, + channel: interaction.channelId + } + do { + + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit Embed") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId("send") + .setLabel("Send") + .setStyle(ButtonStyle.Primary) + .setDisabled(!data.channel) + ); + + const colorSelect = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("color") + .setPlaceholder("Select a color") + .setMinValues(1) + .addOptions( + Object.keys(colors).map((color: string) => { + return new StringSelectMenuOptionBuilder() + .setLabel(lodash.capitalize(color)) + .setValue(color) + .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji) + .setDefault(data.color === colors[color]) + } + ) + ) + ); + + const buttonSelect = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("button") + .setPlaceholder("Select buttons to add") + .setMinValues(1) + .setMaxValues(3) + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Verify") + .setValue("verifybutton") + .setDescription("Click to get verified in the server") + .setDefault(data.buttons.includes("verifybutton")), + new StringSelectMenuOptionBuilder() + .setLabel("Role Menu") + .setValue("rolemenu") + .setDescription("Click to customize your roles") + .setDefault(data.buttons.includes("rolemenu")), + new StringSelectMenuOptionBuilder() + .setLabel("Ticket") + .setValue("createticket") + .setDescription("Click to create a support ticket") + .setDefault(data.buttons.includes("createticket")) + ) + ) + + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + .setChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.PublicThread, ChannelType.AnnouncementThread) + ) + let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name; + if (data.channel === interaction.channelId) channelName = "this channel"; + const embed = new EmojiEmbed() + .setTitle(data.title ?? "No title set") + .setDescription(data.description ?? "*No description set*") + .setColor(data.color) + .setFooter({text: `Click the button below to edit the embed | The embed will be sent in ${channelName}`}); + + + await interaction.editReply({ + embeds: [embed], + components: [colorSelect, buttonSelect, channelMenu, buttons] + }); + + let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; + try { + i = await interaction.channel!.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + if(i.isButton()) { + switch(i.customId) { + case "edit": { + await i.showModal( + new ModalBuilder() + .setCustomId("modal") + .setTitle(`Options for ${i.customId}`) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("title") + .setLabel("Title") + .setMaxLength(256) + .setRequired(false) + .setStyle(TextInputStyle.Short) + .setValue(data.title ?? "") + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("description") + .setLabel("The text to display below the title") + .setMaxLength(4000) + .setRequired(false) + .setStyle(TextInputStyle.Paragraph) + .setValue(data.description ?? "") + ) + ) + ); + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Button Editor") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + .setEmoji("GUILD.TICKET.OPEN") + ], + components: [ + new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setLabel("Back") + .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("back") + ]) + ] + }); + let out: Discord.ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; + } catch (e) { + closed = true; + continue; + } + if (!out || out.isButton()) continue + data.title = out.fields.getTextInputValue("title"); + data.description = out.fields.getTextInputValue("description"); + break; + } + case "send": { + await i.deferUpdate(); + let channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel; + let components = new ActionRowBuilder(); + for(let button of data.buttons) { + components.addComponents( + new ButtonBuilder() + .setCustomId(button) + .setLabel(buttonNames[button]!) + .setStyle(ButtonStyle.Primary) + ); + } + let messageData: MessageCreateOptions = {components: [components]} + if (data.title || data.description) { + let e = new EmojiEmbed() + if(data.title) e.setTitle(data.title); + if(data.description) e.setDescription(data.description); + if(data.color) e.setColor(data.color); + messageData.embeds = [e]; + } + await channel.send(messageData); + break; + } + } + } else if(i.isStringSelectMenu()) { + await i.deferUpdate(); + switch(i.customId) { + case "color": { + data.color = colors[i.values[0]!]!; + break; + } + case "button": { + data.buttons = i.values; + break; + } + } + } else { + await i.deferUpdate(); + data.channel = i.values[0]!; + } + + } while (!closed); + await interaction.deleteReply(); +} + +export const check = (interaction: CommandInteraction, _partial: boolean = false) => { + const member = interaction.member as Discord.GuildMember; + if (!member.permissions.has("ManageMessages")) + return "You must have the *Manage Messages* permission to use this command"; + return true; +}; diff --git a/src/config/emojis.json b/src/config/emojis.json index e4afdfb..35743e1 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -349,7 +349,7 @@ "TOP": { "ACTIVE": "963122664648630293", "INACTIVE": "963122659862917140", - "GREY": { + "GRAY": { "ACTIVE": "963123505052934144", "INACTIVE": "963123495221469194" } @@ -357,7 +357,7 @@ "MIDDLE": { "ACTIVE": "963122679332880384", "INACTIVE": "963122673246937199", - "GREY": { + "GRAY": { "ACTIVE": "963123517702955018", "INACTIVE": "963123511927390329" } @@ -365,7 +365,7 @@ "BOTTOM": { "ACTIVE": "963122691752218624", "INACTIVE": "963122685691453552", - "GREY": { + "GRAY": { "ACTIVE": "963123529988059187", "INACTIVE": "963123523742748742" } @@ -374,10 +374,20 @@ "SINGLE": { "ACTIVE": "963361162215424060", "INACTIVE": "963361431758176316", - "GREY": { + "GRAY": { "ACTIVE": "963361204695334943", "INACTIVE": "963361200828198952" } } + }, + "COLORS": { + "RED": "875822912802803754", + "ORANGE": "875822913104785418", + "YELLOW": "875822913079611402", + "GREEN": "875822913213841418", + "BLUE": "875822912777637889", + "PURPLE": "875822913213841419", + "PINK": "875822913088020541", + "GRAY": "875822913117368340" } } diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index a22045b..80c2c1b 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -31,14 +31,14 @@ async function modifySuggestion(interaction: Discord.MessageComponentInteraction await message.fetch(); if (message.embeds.length === 0) return; const embed = message.embeds[0]; - const newColour = accept ? "Success" : "Danger"; + const newcolor = accept ? "Success" : "Danger"; const footer = {text: `Suggestion ${accept ? "accepted" : "denied"} by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL()}; const newEmbed = new EmojiEmbed() .setTitle(embed!.title!) .setDescription(embed!.description!) .setFooter(footer) - .setStatus(newColour); + .setStatus(newcolor); await interaction.update({embeds: [newEmbed], components: []}); } diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 6805019..0ff04b3 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -6,7 +6,7 @@ import fs from "fs"; import EmojiEmbed from '../generateEmojiEmbed.js'; import getEmojiByName from '../getEmojiByName.js'; -const colours = { +const colors = { red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", @@ -26,11 +26,11 @@ async function registerCommands() { for (const file of files) { const last = i === files.length - 1 ? "└" : "├"; if (file.isDirectory()) { - console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`) + console.log(`${last}─ ${colors.yellow}Loading subcommands of ${file.name}${colors.none}`) const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)); commands.push(fetched.command); } else if (file.name.endsWith(".js")) { - console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`) + console.log(`${last}─ ${colors.yellow}Loading command ${file.name}${colors.none}`) const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`)); fetched.command.setDMPermission(fetched.allowedInDMs ?? false) fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {}) @@ -43,9 +43,9 @@ async function registerCommands() { ]; } i++; - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`) } - console.log(`${colours.yellow}Loaded ${commands.length} commands, processing...`) + console.log(`${colors.yellow}Loaded ${commands.length} commands, processing...`) const processed = [] for (const subcommand of commands) { @@ -56,7 +56,7 @@ async function registerCommands() { } } - console.log(`${colours.green}Processed ${processed.length} commands${colours.none}`) + console.log(`${colors.green}Processed ${processed.length} commands${colors.none}`) return processed; }; @@ -73,15 +73,15 @@ async function registerEvents() { const last = i === files.length - 1 ? "└" : "├"; i++; try { - console.log(`${last}─ ${colours.yellow}Loading event ${file.name}${colours.none}`) + console.log(`${last}─ ${colors.yellow}Loading event ${file.name}${colors.none}`) const event = (await import(`../../../${config.eventsFolder}/${file.name}`)); client.on(event.event, event.callback.bind(null, client)); - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`) } catch (e) { errors++; - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${files.length}]${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${files.length}]${colors.none}`) } } console.log(`Loaded ${files.length - errors} events (${errors} failed)`) @@ -104,7 +104,7 @@ async function registerContextMenus() { const last = i === totalFiles - 1 ? "└" : "├"; i++; try { - console.log(`${last}─ ${colours.yellow}Loading message context menu ${file.name}${colours.none}`) + console.log(`${last}─ ${colors.yellow}Loading message context menu ${file.name}${colors.none}`) const context = (await import(`../../../${config.messageContextFolder}/${file.name}`)); context.command.setType(ApplicationCommandType.Message); context.command.setDMPermission(context.allowedInDMs ?? false) @@ -113,27 +113,27 @@ async function registerContextMenus() { client.commands["contextCommands/message/" + context.command.name] = context; - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`) } catch (e) { errors++; - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colors.none}`) } } for (const file of userFiles) { const last = i === totalFiles - 1 ? "└" : "├"; i++; try { - console.log(`${last}─ ${colours.yellow}Loading user context menu ${file.name}${colours.none}`) + console.log(`${last}─ ${colors.yellow}Loading user context menu ${file.name}${colors.none}`) const context = (await import(`../../../${config.userContextFolder}/${file.name}`)); context.command.setType(ApplicationCommandType.User); commands.push(context.command); client.commands["contextCommands/user/" + context.command.name] = context; - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`) } catch (e) { errors++; - console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colours.none}`) + console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colors.none}`) } } @@ -210,18 +210,18 @@ export default async function register() { if (process.argv.includes("--update-commands")) { if (config.enableDevelopment) { const guild = await client.guilds.fetch(config.developmentGuildID); - console.log(`${colours.purple}Registering commands in ${guild!.name}${colours.none}`) + console.log(`${colors.purple}Registering commands in ${guild!.name}${colors.none}`) await guild.commands.set(commandList); } else { - console.log(`${colours.blue}Registering commands in production mode${colours.none}`) + console.log(`${colors.blue}Registering commands in production mode${colors.none}`) await client.application?.commands.set(commandList); } } await registerCommandHandler(); await registerEvents(); - console.log(`${colours.green}Registered commands, events and context menus${colours.none}`) + console.log(`${colors.green}Registered commands, events and context menus${colors.none}`) console.log( - (config.enableDevelopment ? `${colours.purple}Bot started in Development mode` : - `${colours.blue}Bot started in Production mode`) + colours.none) + (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` : + `${colors.blue}Bot started in Production mode`) + colors.none) // console.log(client.commands) }; diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index ef45875..9a72605 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -6,7 +6,7 @@ import client from "../client.js"; import Discord from "discord.js"; -const colours = { +const colors = { red: "\x1b[31m", green: "\x1b[32m", none: "\x1b[0m" @@ -23,7 +23,7 @@ export async function group( // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString console.log(`│ ├─ Loading group ${name}`) const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ") - console.log(`│ │ └─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colours.none}`) + console.log(`│ │ └─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colors.none}`) return (subcommandGroup: SlashCommandSubcommandGroupBuilder) => { subcommandGroup .setName(name) @@ -54,7 +54,7 @@ export async function command( // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString commandString = "commands/" + (commandString ?? path); const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path); - console.log(`│ ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`) + console.log(`│ ├─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colors.none}`) // console.log({name: name, description: description}) client.commands[commandString!] = [undefined, { name: name, description: description }] return (command: SlashCommandBuilder) => { diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts index 29ea83b..6bc86a4 100644 --- a/src/utils/createPageIndicator.ts +++ b/src/utils/createPageIndicator.ts @@ -2,7 +2,7 @@ import getEmojiByName from "./getEmojiByName.js"; function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) { let out = ""; - disabled = disabled ? "GREY." : "" + disabled = disabled ? "GRAY." : "" if (amount === 1) { out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (selected === 0 ? "ACTIVE" : "INACTIVE")); } else { @@ -23,7 +23,7 @@ function pageIndicator(amount: number, selected: number, showDetails?: boolean, export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => { active = active ? "ACTIVE" : "INACTIVE"; - disabled = disabled ? "GREY." : ""; + disabled = disabled ? "GRAY." : ""; if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active; if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active; if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active; From 0941da49290c2b7298329ac972eaff210d62282d Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 18 Feb 2023 20:28:04 -0500 Subject: [PATCH 39/74] finished? --- src/actions/roleMenu.ts | 3 +-- src/commands/mod/ban.ts | 4 ++-- src/commands/mod/kick.ts | 4 ++-- src/commands/mod/mute.ts | 4 ++-- src/commands/mod/nick.ts | 4 ++-- src/commands/mod/softban.ts | 4 ++-- src/commands/mod/unmute.ts | 4 ++-- src/commands/mod/warn.ts | 2 +- src/reflex/guide.ts | 2 +- 9 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts index 16689b7..be58d99 100644 --- a/src/actions/roleMenu.ts +++ b/src/actions/roleMenu.ts @@ -182,8 +182,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti `**${currentPageData.name}**\n` + `> ${currentPageData.description}\n\n` + (currentPageData.min === currentPageData.max ? `Select ${addPlural(currentPageData.min, "role")}` : - `Select between ${currentPageData.min} and ${currentPageData.max} roles` + ( - currentPageData.min === 0 ? ` or press next` : "")) + "\n\n" + + `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) + "\n\n" + createPageIndicator(maxPage, page) ) .setStatus("Success") diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index b500642..847e66c 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -185,7 +185,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Do not allow banning the owner if (member.id === interaction.guild.ownerId) return "You cannot ban the owner of the server"; // Check if Nucleus can ban the member - if (!(mePos > applyPos)) return "I do not have a role higher than that member"; + if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`; // Check if Nucleus has permission to ban if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission"; // Do not allow banning Nucleus @@ -193,7 +193,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Allow the owner to ban anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow ban return true; }; diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 6743ebf..45155aa 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -187,11 +187,11 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => { // Do not allow kicking the owner if (member.id === interaction.guild.ownerId) return "You cannot kick the owner of the server"; // Check if Nucleus can kick the member - if (!(mePos > applyPos)) return "I do not have a role higher than that member"; + if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`; // Do not allow kicking Nucleus if (member.id === interaction.guild.members.me!.id) return "I cannot kick myself"; // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow kick return true; }; diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 407adf4..b8f592c 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -375,7 +375,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Do not allow muting the owner if (member.id === interaction.guild.ownerId) return "You cannot mute the owner of the server"; // Check if Nucleus can mute the member - if (!(mePos > applyPos)) return "I do not have a role higher than that member"; + if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`; // Check if Nucleus has permission to mute if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission"; // Do not allow muting Nucleus @@ -383,7 +383,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Allow the owner to mute anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow mute return true; }; diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 9d5aa3a..792e535 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -203,7 +203,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Do not allow any changing of the owner if (member.id === interaction.guild.ownerId) return "You cannot change the owner's nickname"; // Check if Nucleus can change the nickname - if (!(mePos > applyPos)) return "I do not have a role higher than that member"; + if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`; // Check if Nucleus has permission to change the nickname if (!me.permissions.has("ManageNicknames")) return "I do not have the *Manage Nicknames* permission"; // Allow the owner to change anyone's nickname @@ -211,7 +211,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Allow changing your own nickname if (member === apply) return true; // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow change return true; }; diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index bd940fa..d6ef481 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -187,7 +187,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Do not allow banning the owner if (member.id === interaction.guild.ownerId) return "You cannot softban the owner of the server"; // Check if Nucleus can ban the member - if (!(mePos > applyPos)) return "I do not have a role higher than that member"; + if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`; // Check if Nucleus has permission to ban if (!me.permissions.has("BanMembers")) return "I do not have the *Ban Members* permission"; // Do not allow banning Nucleus @@ -195,7 +195,7 @@ const check = async (interaction: CommandInteraction, partial: boolean = false) // Allow the owner to ban anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow ban return true; }; diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts index 4327019..826ed5c 100644 --- a/src/commands/mod/unmute.ts +++ b/src/commands/mod/unmute.ts @@ -146,13 +146,13 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => { // Do not allow unmuting the owner if (member.id === interaction.guild.ownerId) return "You cannot unmute the owner of the server"; // Check if Nucleus can unmute the member - if (!(mePos > applyPos)) return "I do not have a role higher than that member"; + if (!(mePos > applyPos)) return `I do not have a role higher than <@${apply.id}>`; // Check if Nucleus has permission to unmute if (!me.permissions.has("ModerateMembers")) return "I do not have the *Moderate Members* permission"; // Allow the owner to unmute anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow unmute return true; }; diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index c6b4a56..ca3bfc0 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -291,7 +291,7 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => { if (member.id === interaction.guild.ownerId) return true; // Check if the user has moderate_members permission // Check if the user is below on the role list - if (!(memberPos > applyPos)) return "You do not have a role higher than that member"; + if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`; // Allow warn return true; }; diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts index 2df8f1a..a3027e4 100644 --- a/src/reflex/guide.ts +++ b/src/reflex/guide.ts @@ -129,7 +129,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { .setDescription( "Nucleus has a content scanning system that automatically scans links and images sent by users.\n" + "The staff team can be notified when an NSFW image is detected, or malicious links are sent.\n" + - `You can check and manage what to moderate in ${getCommandMentionByName("settings/filters")}` + `You can check and manage what to moderate in ${getCommandMentionByName("settings/automod")}` ) .setEmoji("MOD.IMAGES.TOOSMALL") .setStatus("Danger") From 8a8dbdfea813eb971fb5cab28bd8ee837d313d24 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 18 Feb 2023 20:39:55 -0500 Subject: [PATCH 40/74] finished? --- src/commands/settings/verify.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 920beb4..c440b75 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -13,7 +13,7 @@ import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import { getCommandMentionByName } from "../../utils/getCommandDataByName.js"; - +import lodash from "lodash"; const command = (builder: SlashCommandSubcommandBuilder) => builder @@ -33,7 +33,6 @@ const callback = async (interaction: CommandInteraction): Promise => { let config = await client.database.guilds.read(interaction.guild.id); let data = Object.assign({}, config.verify); do { - console.log(config.verify, data) const selectMenu = new ActionRowBuilder() .addComponents( new RoleSelectMenuBuilder() @@ -45,15 +44,15 @@ const callback = async (interaction: CommandInteraction): Promise => { .addComponents( new ButtonBuilder() .setCustomId("switch") - .setLabel(data.enabled ? "Disabled" : "Enabled") - .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success) + .setLabel(data.enabled ? "Enabled" : "Disabled") + .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger) .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji), new ButtonBuilder() .setCustomId("save") .setLabel("Save") .setStyle(ButtonStyle.Success) .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) - .setDisabled(data.role === config.verify.role && data.enabled === config.verify.enabled) + .setDisabled(lodash.isEqual(config.verify, data)) ); const embed = new EmojiEmbed() @@ -88,6 +87,7 @@ const callback = async (interaction: CommandInteraction): Promise => { case "save": { client.database.guilds.write(interaction.guild.id, {"verify": data} ) config = await client.database.guilds.read(interaction.guild.id); + data = Object.assign({}, config.verify); break } case "switch": { From 4e0b325fe20d4f1eb86477fa86e0c80544570288 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 18 Feb 2023 21:49:38 -0500 Subject: [PATCH 41/74] finished? --- src/commands/server/buttons.ts | 2 +- src/commands/tags/edit.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts index 4a299b2..bc90983 100644 --- a/src/commands/server/buttons.ts +++ b/src/commands/server/buttons.ts @@ -224,7 +224,7 @@ export const callback = async (interaction: CommandInteraction): Promise = } } } else if(i.isStringSelectMenu()) { - await i.deferUpdate(); + try {await i.deferUpdate();} catch (err) {} switch(i.customId) { case "color": { data.color = colors[i.values[0]!]!; diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts index a6e23ba..a80869e 100644 --- a/src/commands/tags/edit.ts +++ b/src/commands/tags/edit.ts @@ -20,8 +20,8 @@ const command = (builder: SlashCommandSubcommandBuilder) => const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; const name = interaction.options.get("name")?.value as string; - const value = interaction.options.get("value")?.value as string; - const newname = interaction.options.get("newname")?.value as string; + const value = interaction.options.get("value")?.value as string ?? ""; + const newname = interaction.options.get("newname")?.value as string ?? ""; if (!newname && !value) return await interaction.reply({ embeds: [ From d0a166dfaafd8f2a09a7dbe741a2f5b4ed07922c Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sun, 19 Feb 2023 00:04:53 -0500 Subject: [PATCH 42/74] not at all --- src/commands/nucleus/premium.ts | 2 +- src/commands/settings/tracks.ts | 17 ++++++++++++----- src/index.ts | 3 +++ src/utils/commandRegistration/register.ts | 2 ++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 2f20d2e..e0d8d8d 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -9,7 +9,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => const callback = async (interaction: CommandInteraction): Promise => { await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) - const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id) + const member = (await interaction.client.guilds.fetch("684492926528651336")).members.cache.get(interaction.user.id) const firstDescription = "\n\nPremium allows your server to get access to extra features, for a fixed price per month.\nThis includes:\n" + "- Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n" + "- Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n" diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 612d069..8d2d59d 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -152,7 +152,7 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera let closed = false; do { const editableRoles: string[] = current.track.map((r) => { - if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position)) return roles.get(r)!.name; + if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position) || interaction.user.id === interaction.guild?.ownerId) return roles.get(r)!.name; }).filter(v => v !== undefined) as string[]; const selectMenu = new ActionRowBuilder() .addComponents( @@ -217,7 +217,10 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera ) .setStatus("Success") - interaction.editReply({embeds: [embed], components: [roleSelect, selectMenu, buttons]}); + let comps: ActionRowBuilder[] = [roleSelect, buttons]; + if(current.track.length >= 1) comps.splice(1, 0, selectMenu); + + interaction.editReply({embeds: [embed], components: comps}); let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null; @@ -233,9 +236,9 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera if(!out) return; if (out.isButton()) { - out.deferUpdate(); switch(out.customId) { case "back": { + out.deferUpdate(); closed = true; break; } @@ -244,14 +247,17 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera break; } case "reorder": { + out.deferUpdate(); current.track = (await reorderTracks(out, message, roles, current.track))!; break; } case "retainPrevious": { + out.deferUpdate(); current.retainPrevious = !current.retainPrevious; break; } case "nullable": { + out.deferUpdate(); current.nullable = !current.nullable; break; } @@ -271,8 +277,9 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera const role = out.values![0]!; if(!current.track.includes(role)) { current.track.push(role); + } else { + out.reply({content: "That role is already on this track", ephemeral: true}) } - out.reply({content: "That role is already on this track", ephemeral: true}) break; } } @@ -330,7 +337,7 @@ const callback = async (interaction: CommandInteraction) => { .setCustomId("next") .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji) .setStyle(ButtonStyle.Primary) - .setDisabled(page === Object.keys(tracks).length - 1), + .setDisabled(page === tracks.length - 1), new ButtonBuilder() .setCustomId("add") .setLabel("New Track") diff --git a/src/index.ts b/src/index.ts index a3a8d38..306ca50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,9 @@ client.on("ready", async () => { } else { client.fetchedCommands = await client.application?.commands.fetch()!; } + setInterval(async () => { + await client.database.premium.checkAllPremium(); + }, 1000 * 60 * 3); }); process.on("unhandledRejection", (err) => { console.error(err) }); process.on("uncaughtException", (err) => { console.error(err) }); diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 0ff04b3..693ad00 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -214,6 +214,8 @@ export default async function register() { await guild.commands.set(commandList); } else { console.log(`${colors.blue}Registering commands in production mode${colors.none}`) + const guild = await client.guilds.fetch(config.developmentGuildID); + await guild.commands.set([]); await client.application?.commands.set(commandList); } } From 8c4e83b6000b87caacfaa78d3f3e9968b5f21beb Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sun, 19 Feb 2023 00:09:49 -0500 Subject: [PATCH 43/74] not at all --- src/utils/commandRegistration/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 693ad00..a4ede3f 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -215,7 +215,7 @@ export default async function register() { } else { console.log(`${colors.blue}Registering commands in production mode${colors.none}`) const guild = await client.guilds.fetch(config.developmentGuildID); - await guild.commands.set([]); + await guild.commands.set([]); // TODO: Why isn't this removing guild commands? await client.application?.commands.set(commandList); } } From cfe63080650db7b47dec872d8120242f63d60de5 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Mon, 20 Feb 2023 08:17:37 -0500 Subject: [PATCH 44/74] Co-authored-by: PineappleFan Co-authored-by: Skyler --- dump.zip | Bin 0 -> 19058 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dump.zip diff --git a/dump.zip b/dump.zip new file mode 100644 index 0000000000000000000000000000000000000000..c259f0e7e2d47f0739af5d8cfbd4ec1bc18247c1 GIT binary patch literal 19058 zcmafaWl$!~vL+1f?hb>yyE_arxVyW&z~JsO4DRmk?(XjJ;_mLUbI#ox8|T}-u^o}! z{i8CXp32InGPAQ4Wx>GFLH>3139G36`{jS%5dJ<*Tx=Z}SuP3-{6#ula~E;go43`WlOb_wa8*H_}2!@l@M(8?5SV9Zhp zBD2AreC=;Gvr$+b;37B{;g%NyZ**CH{Wx5Xc&KE(uy{YwTT|9d*fDlZ4gr5GJAz`a zLn#b~N$}00BHGEhkbiVOO(1=KUcQ{YcWq50byZ&pymg%)IbUwBt?_IxyFJpstX*(+ z9!zX`Z7I8LxemnWKG*5R$nrJ`jKj?Y#x;|C0Ui+>%&)NZb zK)xPyk1{-6`K`}}U9;QoGMwEmTP>_VxgC=AT8@%^t(VImZ3tef*1LROe4eB0Pu)G` z1&Ximc^kW5^C~|cb$>mv0-7(sM##T>5Idi0d>yO-U9ab9tiWKg@4fqq88SbLlJG-c zt*91Qtip#RF)mOPIZO~Za0El*FzjWLku4H0ll6Rd2-0VOkGqnaALUqH_kV^j_GAG+ z6FL?Q>9`-rc#S<9GqgB(Iu7j0rO)donbWt8B7vT)B9V0G(ffyRK3fhn&+dtrL)O*n z?I^%mw~rTl{o64?k9}{!PBC>hHF_{vGgWFuCQA$A^~}-m^N~3O?&>ts)D5bFj0_Z= z6cYnY?H#VNev{qu9o4oy-|ybmXl>PZyjd>S&HA-xY0Vi;Vn?AoyaR?qc`biBU=nSN zmnRi%)3jLa!VZLOlF?#ZJdm=of2@ThJRLtEp-%X>ku{oKjpu68-MS5$)gx+l9(>D^pn|EMGQRU*ZxeO4EETUBWz zZ&m^$kuocZ{zjPiaCbWVf|TB$+WLi_*Ve-}5>Lg`o-b2qSzp#$C0qJ=d-4c%!y3F- zX;d6J^{NrF5`*M;e5soq_eo{7S<2_RX=6&Z{r5vO{_L7IJu0HQg>Mu5q5Lg4K+$!; zZ-&_iArvA$BkrNFG?x2b!$LX^gYeElSZ-B~U8*JcEmoVn^fj7y(i%(L$g;o;qq|qr zNv}=8tlJ(5fNzs=`eiM7TZp(cNI;6z(Z(RU>GV=BZiGDwhlN(jwG$>9F&n0th0f_H z_2wx#{5dbjcGYO*T;>h!(MC56@FKbEE$FQKx$ceV^-+*7pbIrgLYPW16D|-`IX^A zIY9s8(@?=yBDv{g$*D+<%51c^<_f`BQX*|QN|?`>x>CJ^UBVg@5{f$-Uv?6CWvu2K z1i@Pvn|TAc-j>_t#5hNOe6#mm(TCulr?)|*%GpA~inXvtxD32FX|>&RWGN|8Ip%s2 z**Fq9ij^rsP+TP(-Z~8?LLiBW0jZ9&w(H$+_51CYo!C>>)-w=nPTj796wBQR==;s5fZZydqbib4v8sx1}A%~bz zfkip}2%P|#27J~@SK@h1MVLKd@^+m;TR$t<6fp{|st%G8InF{hGI{T$aHL7JzX=sNZeHsAMRNR# zv!F}wr@D>EQ?Rkm)h&NE?`{L<*A!|dn*T2$l3FQhmzLJ*R3jOqmL5=S<|3_1P5L-W zO{kC1*~k^cKT93NBE=vRBAB*NMxYaaylHupt0BU4 zLHxnUKpl~EYw7W$RqZ7Mlc3#X zf*sFc{H8`6-L0axaB`lb?@v4Bj)X(WvK%oRDn9mFk<{( zr0Se1i&491s<=jbY9GOB0nnYWS*9?I|2RPPi#PMSpeumu+fEtbHJVI z2wYvEJwqCsY0WOiIo*QV2;4ca(EU+~b&UAHh^oP_X6{;sxVRkf)OGX3S>W$`b7Y|+gFtZL;+HR;tdf&eK_!t zC0rK3;Y5+lk*srg6z0vfL#HgnrcAOek|3V@!|W!mz(0&$3iz{MI}tuEq1FLJ8u@3P zSiA~B9*6?3?jive5Vjf3A|BgpW@^1FBzhqwmG>$?jiE$1`D&f47mc!%sTLXDfOrBA zmEvtk46`mrITX45G2AsO=XC>8^fRJJeqkfr^mvm((DktPRG9h@$rgsh6i6W*h%4kK zW|^1aUoG~mZM^yoa&8Z(1jR9<18d>8*n}*{$|tzkw{f&;VklYK3u7t$GF|se=Gw`z zFy5FSP+CFm63Rrjsh1Gbn-9Kt>^bJgOY(%oiE(hdNtBaWOrgE&GPU5!-yYM0!9>Ku zAp(>?dXoL?m>}ycXa}E@It(xnG_cBytrkRCrE(I@>Ssb~mMjfwVCvgto{p;oS7I-Znog=T^X_6LrxWEQ zu`vte?-jKNZaOsmwg(A4!fql}a$>J9wXpd~G1HpK5x#NeX=Xmr@^kFb0CS(--$ULm zE!%$6&5{^vRv!bUa1l>vQZtAz;Tho)WbSHH6^S419-mIj68-5;QB<@7$}E%+7jZ*E z3rr19l=YBLu}2SzLG?33We;A0Bbq z$YS8EmtVNPh`xT|TO`_e2%vhasNF7{fmSA?(8_~M7Ezh9v@Hl60gZ^6I%1kQlk+5p zWZ|bDvKDe??LH`TQTUaPxu>lQaPsT6sx#d*(fqhdKd+ohz$J?{2s*Q23OCu^q`=si z-75R3BUX>)8x|g2T@mpTakZaa4?ey0MNnj(bgMKybUkun3$jh(m9#kf?xDY z_4YV_l{;@PH0*>!tH1;uw1*qO&qYkp#MBj$Vi@_?QR-q$yWy~iS~NMk@6eL@j!LJu zT;_PUYQ&)6&Pp9we|&E>VnNGOvqGrN1{+{C5IZ`7$sIs&9a&s5^iV!Zcp``q*Dl%? zgB#T>d^cN0D94{ZZGT{VVQPx>UKTv{=mm=a`@Av{%*qjSY9vh?9PWa$$qTs zNaKi1V&qK|&dgUl(>_s$*{Z& z+lE<~K6EM}pcnJI?U7WH^I<>MhW9ouFxMKeU=?d(sFA{=50S0xbqt+>&9Pp-WQmaU zRx%`GB$mi_^<09;$&X?gd^D;=e3blg`60>IBJ~tN-X_I%m>OG4){f4@6VN0Ew#z-o z2M&*@jHT;fOrwya3Gt)Sg)N(DdeR?leq7+NCPVeI`!vklR?KZsbsfN{lh^+&4IoQ< z2^$mgXYEZSJeq2_n7VN>(-)!0k2b%3>kcW4|pKm{AUk(gvV{&y@|AbD-fWo$d0upAg>31l8P z%`dSEDMmk<B+=Dx8Rs+h`mlHLVL0`!xdgA7PV0-ox?)@MyPlHsRy(?mO1g>#>)wwr$y(dGl5~R*H$O{W+12 zRd9<%1nUSbMo&P=?W7gF5KgQ7FuI5=N_bkj*kr${@&3K)a)4y_WhUMnutv`99#uE< zcdzO($srAy6Gjx(!Rb$9v`uQhfu0`iX_zzZh<^6tGVUR7Br8-Iy#~W~LGxwY`V&>v zaHT&yX?fUlMoX?kp+Fer z@*sdo?FAEcI^4)m$q5-FQQNe&~lw2D2g@nqx4IMhA5&1Xc*BOB4;N->J^2MLEr`3H$K%yD&`Dav<*}V zzcvtmf5oE%%k|wlk%0=4GY_gHH~jRidc^F0R-iHHr>1GAa2wP@y^-4B1{XFb+wfQx z$Xk$~G)u>wPsTB?i#1|b`jkGqhZ#&JB zJMMCLB|P`31@80H(+w~ zP`k7!oER{9qsSe<)b^JxpGUd68r~m$i<#T=> zPYN(y3x@)y##nKh_Pkq|%RodzA)WLzNi>8a?x{?NXe;^U8YY3vU;Jn!m61P?y!Qj+ z4hZFQDJw`K`hHbiK-wq4-33nI}0zy2X?Kr`wHNb9NA`x~uw_RiMQN>>SLd7yr5* zY$H2XB39J_8P6uu)n~CRpQff+0^e;K=_r$0^vNBC2hQ?U;h^gQZ^3hL4@{@c5L4)2nAUXzLuCX8@8r<9%oF?UrzWuA=QNd7FzxGZVQo1K zov;9kBWB!d)@)2m+4^sde_lLoc;7?-ZQ;}uEH&~C+-VDpZzuAMA5A?Sb42=g?n@7U z7>27ha2s*vy$$lW5uxVNV#Z*Mej`%g#nV=;!pP2TFlf$y9L0g)(3p&#@}Q8F#gFwz z)eWa_jLm@==4wt2d^Xv9Y3Wzk2)PXQ1cZtm+nh9NZcdW8i5vvBTSu}o<`c6xH=MI0 zI6gGMUe&WbeWyYefq|r1y{z7Lo$MF53$m z7Uo#fJ7nJo2V$~S)D4VL+`wW=G^AJtmMSv3X{8tuyGvwD7A@(131x?9glD?m>D%+A zBIURL2#VqNJpUbRxoFdEjs!y0HV<1^RWI3MnKVgfLx)&(0okn#)<8uWXmg$)0<-M` zXqEgmA=eB$+bJBxunvt8aD+ORQZDLu5LKK)k|+$egv&>LmQyE$zd;C+x3?Aq60GZJ zu`^QcFQRJws4xXnU;pCff{j*-!iO`BbgbV)N3b^Hy)j(6K^R30iek%q$}< zPDs*x1)E6%F@*OJ?muH2dMUWG}nD=1;6iFa{0dPD^LH{NY_Qs+YDB$y=0YWj7GB!5(fVZ8DuPkDR|6A;9IsuzO|sigeJ$%yo#W!(Jyx0gHNsKpz&b(U721t8&f6R`s}$28Akh;UQ>=CvF30 z508BQx!DKedapz6H-%CpV7j=-A)^N%*<%=4rm-V2+`5g*%u463)CMMJkb7Uo4l zR=4{?}XPir$NGd#u9u+hiO7#PFb8K#jN?F)9FRIn7{eLLegeFG9!LeendmF zyaTfBXA=WqGQ}@gDc9L^SX5<@Gl|bvHq}mcx|5V~n8+TqqQ_O(KEyktWS;En|A%51KarUGmrh-`wAtkZV-8_h5)x z!p*i@GEBZnfsR<=SN|?|g7kK9UDLdzwW@b0M`)dVgCgLtp$?R z`>{pZcH3qp09KGa%+8KDm+MLVF<9qOva`U;@`8O0w+ia_SxV}Jz zV$J#S{Ph~SV=Z>f{xS0RV8rBhJv5Oh1Tb9R1J-6_ZHvpaeAq>jRhik3^&3w6yFHB;&&b0LwF$01Td zfJ3IdH1|^_Sd*w}P((LJF=+h|bl^*~*Dta*+Na|*XkEQYaL$UfHoOa4VTa08b-(lJ zrFC#q7C&M!IBP||09)|y8HPFr^+b3jEnbj3+LAX2%aQWl*~q%dx#NUY={AH$_ZxS{ z0ISaF3Nr$FXKq#P(J0GgBU&AOWm;_lr_mrxmnn&uFb4cx3W3z$zKcM-*-q!*c40H1 zxXJ7;m;o(}JTw*+VG3K^`dc(VXy6bzc;5?fZQB?en`hV`L%?vhKU%(1)-|e4K-<6E z=k~Jzl!K-wxp|&i(l(T!B1znbHXuXYWdWJ|7idOkGHJY&>aaOn#}ns2L_pSz_*vjJ zoq92(XFqPEi|xhS9teS`xqCxdi^NhgXRA=dK{Wt|)|cH-C(rB@-bXs2jiE{bjU+wmTc8PId#a$g0El#$ax+ajCQ1*}+C zqXk=gMrmBJF~Y35ZY&X`{5<*2u@iFW-?*;h59_6O$NH~6pONh5Q=yLa=tY*J^=oo; zTQ$^bBIP7;EW4bhMWaWa3D@A2=-PrX7cZz|M#M{{SYtDk0Q#Sge`6}$ z?;=NsxP=bqQ*PPLGBlo{9RX3f>iTligXA!s7#R~98$oimbS@O;JMQgp2qHxyL3>BL zZ|&n>=c6KkYLr~jn&&Re_P$t>J3D@R0_A=?QOe;lM9gf10bQmI*(+-e{BRl_fMgxj zIOC++&`bzdd4ey}-(d1~+7lotb{spTy?w}eJTbBs!7>g$Z!5Z%J(9gKYtry2AXKS| z+&PVSekf6zTaGF>fW}z1@CZ#|DsWxxTRZvJTRkhGbq%dimZJb_18Em!%<4P_C)>4S z?}GK#%!O4siw%axGz);=o8+qaLE1Rm?Yn7SuD8laKsqtET6o<9MKo)pe(g@@RrY-r zO#vF@@p*^N{u0(&Te9YD%=f7}2lP=1JM0p~(Zpb#<$=xjL%2`ju*(wYOa<2zqi3T( zW#cQ2m;Is51F|cHy;gzzAgOLJW01$me8k2Z(7o5LsDybY45K@anARB1qemQhZ`1s3 zvSP;-7j4c3MkddolSO1yqblFp3t+#qs)L;D$fZ=23(9IS_8ZawP z9P0bNDRY=5hR%KzK@(KK!-EdgBi3R_0x{b5@L46@fVeTXB}1B#D}eA$M4Sbt5P~+0 z&jJ?)nZfKKWc4-Q`~CUg=N-O`Sn^^HLNa#GI4a1T-}MaQ z75J5g47>`P5%)QXi~G8ukowK1lv|-@U*3EWPl^+;j3ptmSiE|z&Y3!+5 zd|r{n4>w_UR^f81$GN;CH~5mJ_Z7CXj^*i!lR;Rdg(qgR3VrbNvh5RWodyZ&xQki@ zs_k_32UM%71M#xSusStd_&15(%T*Nkuq?tdc}EgbKW4I&L&WJZn_%Z3wEo z3L#`gy0(R!O2FM08PlCKp_P7h@1rzjGFJUP#oDI?Da71#f~G1WoJV)1sTqdRF-)Y- z%I{QJTzAD75+h(R2t_7M&YZ${!Knw#H|6HZyh$M-)BuBbZfE7ocA{>pw0~(8E4P2o zKRk8BCu{r^a=_$(#e?e`m};k%lnqVOP|u|My3j9R$v`trP&fd0N0;3^Kc_CA>ry`J z?Zg(N8w^nQ6t|zX6d=_LMTk$o;sOBQ_BAdmsLu8 z6U`mIEV^s%b_M1JH>*n`(rnf%d4ypiR-}{KZX`4hp$Rw*v(O%49XF4sEIxT*h@6sj z}}74WmWk1o|4S6U>0NHAl=W>UA=A;4WEWFswgq zx?b}JF`KB>{Ypdh@-vnpGjQU_fy`uyVcs(1b>(eYxr`a6j%g3gdgJ{2mAYQ?$&(bo zk#frR7Gx|D_ikstjP~lTDxMlVS7yPSo)eHGJ{S4)g@noQorPW+mV|ltBeQ~{P%x4r z-zX(x_7mA?Feevo7&eZ$MoK3vXRnXs25G+UXI~Lj7z?^|2+t9(zShuDQ@Wvo_F&LFAEqhAG)%1!MqA+IV=I%UCe78k&b ztS$Ru)>}J>;bE1Vr9@3G7gJv5FRK}y3pBE;H`OI;h3oa;p&{iy6e_=Ms60wwcriev)?`iU$!K73X1m+~T9KBP0we z9=~YX&RW&c%fDAS?d21#gk2ta0*1wp)vlETxyU{P?|Jtekz7Vwt-NGf(}D_?8et6G zJTn_x=c3BfQBQX_Q$geM2r3tc(9MI!yOjP$iB2DcKp2!ZD-A^sEM2>h`K~?6y$3ofFQk_%{cUzxLZ3kK_`oxg9MJ< zR$jd|AkF?#o_JMDe{j&8Dw;Xh*QaB4UZFVsh|Ovk>toy4O*EW2y5p z;FMc(-oZ70lps-`&nF)l=q7&5vYfj7Q>Z!Wa>UDvL>fPR2asDnBRW&&Xr*XX(8mEc zr3?`ix2mk}Z-{B*2X`og!dk1p=@5?$d=fj(Sgc(rjf zxPzexc{L!;dzObch9*!Cad_-@;`?{B<`s$Kc3cgV#`y1P1 zpH4~2^rEMGZKO22m5~WLJ*8nUz75-kwzxP+N>X zGyfR*BGDxX^hK0R`ui(49vrNxTp%AOGOKo?y0l%(%K@J0v3&T9Baaie5Yv5FsSVPd zrnbB{?xWOTZ?fwiT4bD~1VP;%nXQ}Q(+|)k$W^<=zf7+laVhlct+*-I)9LpmC`y}+ zjqoK6@sO~(`86nR0k|NyNvN!r(OUE&Il`eOWtjBg5u|YY?$l2xuX+6jn6cZe$M`Fk z^qG&n(=G4YZDY60w|nMQ4s8n@JM(}r*#=lC|GF4B7|JbD2YcXJYJ5=bT9Vxa@=s$s{Pj2m~eM`*h9( zoDoT4oT#C(sHHfPKo-|4w~JT!DEM{tx0K4EsVWe>4zITB)NbD|=f9P}CP_EFAVkY@ zx7TptK@rGnTQ%0J)m*9Xv?DlnW;RlIEuGGZD)UN7lM*q?>o7z*NK(N9ZHtiPaYAx3sWtgpl zm?R~KNsv{=0mKtd04zOU>+_5rz%sqOBI zRGY41ax=1l;3G+8m+j6`d8IS`v6Td1K~Z?jke1QtK-1;Sw{r?Wao7E}_H@_XcAtWe(FKUquEg)bUM0@*q~%*{FDL{ zcJ9$^2DR0eUPo3L*aG&JXMet90;)59y;EL|tLtRx#V-2gSd{tm9MPmI9p-SFv4>k% zdy0`t#UfcdsH~fyGGSpBWZ!kxuLnj{U%YO1vT8WC@HSyhCUsr@F|GSD0FC!x#xW>sTB#MY$DwHb1lmt1esSJtb1Rm0@f zz(lB|3&zWebG;*YDZ#ClYS-oX)Ubm<$}|bmZR853!P=S4CuaQ2Y8na?@$%^$Rov%W z-Fywv+B~~DBq__uh`SU#ZgvWGnWp5$U#E2)XAU7aXK!_K>^vk&7PzlO3K^prQzb|? z76z$Z&@?{c^4c8^KebJcOOJu#F@*w$Te_|z1WOiBhWWCUu~ zn;Moz&D9`yO_$vmroy`0CFQd)^Wqm#&PRambu!01Rmb_03nj+p&)kkR)U!1NGEiH< z8bTICUvBi@{r44?w+Ke0Hz#Tp&$6#j)T9Qmj8J9{kKtPqy{q!@>XuTp-}P<0HKH|dtusp7M zq}#*Ks%mM$s%?_DPdaCzGbkvZtmxU%0uG>sto})nY`ushh)IkYdXj&aY$pzE-R{tP z`eOF4y1&GQ7cgzDN?ffJA*@j$X}q}Zu!Z)C+#=1Od9^?7T(WXJXiP2X-18>Z$0ay+ z@Nd(Op3m}^&=b*rHeXj%z>kLhZMts!E7bp==4)G1fT4*Yz>vY}Zx^=dL@i4!MV3fz;)T^*Fk5(uy zjz|DhvaJQrU2`!s%F22VHmL=26->t#70Y6plEwjfWCzc&4E1FyCh1(n5-g0#xo5-=+;GP<3+-3Jn>7s!92Q77Rr#eoL_QDg!E!Tu*2a~De+6X$=okXLH8 z|NbtCQ=O@KzGyT+G6G^rSHpzrseKhn9yIm4KK2}~35f`$#I4D!sx|e}u_2s)hg+czKmhx5&iiv<}em$^w-H`TAM>awoktMV#ALe|G! zq*GyM4?gbCoquuco-04k z4_855YGQ3k5v*()x;1Gvo_&6}qO4Y$KJ0%=fSyhbMY;-9r*pr})Aq{^zaF7`YOu@c z6x@K56}s~Ms?11|*FtPub6ex~1g&I!9Ugk5BltP|cZ>t}Rvd z+Lb1tGXxp&aZrvu#cCADswJGmA}nQ`V7gc30sr75sms5Bs-#Ja;G?S;p;trGV$kqM zhi7j!w8wqrsr<5WZ*RUWz3FUzecsjGt910{p^>MH;ptc4c_Wj66j8(U7-y_~AhU=j zW5gI|FRgLIy$Dol;_ri-=RXo~xshXT8Xhok$V`xl$wUbUX?Ve3rbk;4lfm^O?&QDJ zy4>V@e-~iNl;qv|+xSmbqV??`K$*5&Kj#P0(F#&s&G^+2-`)|m!1Zb5Rwh)Hy!Y^5 z;;Y2I-0)r`a{4VSjpRK-nY6G|WaIN$fBnKBq=Lt-+z~&RkNxGKWX)(FYhPI(#;-v~ zFg8ZneE7=(*^uS75`9Tyst}QI8R7tq1>Miy5KvQ*;YAGsVN6bL`}0LeO_;c&r`GxI zw;uFQ9p2xjPDK@7rJ#1f%}d7>Uf@!+Ew>yNx^thgudc#e#i1F!J^+d_;+|IIPzEojVsQ5!r;_mbdihHw7y8j z#OY}rB#0AI;e4U81jt_hB6WI1D!vQYgSv-mtt;^&rkzPMK#-atCfY&nL$Upn$lg+} zE(2$mgSqUlA;bIvqiGT3Q)BY1q1KfvWZ2p)Gg^1+e;%Be0NJ?0{{<>Ue7WRE3w{K# zry$bg2hj?GHZfj&4Z?|VJPud&kM0Ye zLR?ZR*7$*~1-!Tkar8;IB##|rgB*_g7budU=+aUXjWBsrcYw5y#1Nsc#`(v!wgkM} z|5Xos;f-Qu)bG9Z2kF9&s1hVWI>$0q92u89Uep4IDN_6O8Sdlv`6s91BVR;J3RNfn z+djnIwBLE*@}@Nd>r1Eyi8L8*0F4pC*qEdsY7xTNOotb;!eHSIDo%OD`*xrrIwbk@ zwZc7%SX`|H?6|7yr@u|CN$eJY;VVB``~^x@JUn9k?~bZq`{R3Le=QgUbwYs6a=MUr zlO5aZ!)M?U<6EGoM4O!Db$oD6+0(D^uc9lv(|1N>@T38{ zvHt=kMM-W^<{P<01jbTFRHlz^$TU~3q)|KXo)s> zH9~i{)@(ikOCom#p%-MH^;NpQHGV4|dX5cYy-m#Lhh_3377COdqcoA-A^QAH!^TL2 zPtYW}&L;k%AW~E&AvK7VI zO0ke1lKlR@XMq4$4yS(?T4Dl{Ni2_agVyQBGn)8y1U9Uch4dGh$&9@}_yoko*Wb8| zcSZ?xItNBZfk|@B@)cag>C0Vc2%NZ`6k>muC`v_EY!jJoqQK z%p@0yG`Xy1c?F~&N0!Y`Q;nG-?bsgYpGB4pk%O<4jF$vVN{mC|_*DsiT4AcogWSmw+q(6MDXkTAG5P%hM`-%QInF!%$n8c(-@7ud!d*s4ulyo7h zXG`hmi+VxivLUw3QBygKL4(jpiV(o_9Ld?GW^#(Q@ng<1ATbv-iSbK>^pRDh5g;uh z*D}&o9uC=vAt^112)=BE^;d-Y5i>;=5Zn+Id8J4>V1C6eh)J@@-tby6|7DCa*5e)|pm#aG;*FS*}B7 zV%n|`PU2KKHfl5+`MrE0`YcZFwf#Pyv=z7O@p$~OwdiP+xohehK`(&sv*1`JT%dg2zCq@5Y@0HH*n3tE?jeX z6CF?$wdSN2Vh%7R*q+QY+@>8Y)&BWGujS6?kHYKZ5QO{i4qn3y6nSHPe4BmjBm8bk zI1qp3lYVj0OpB{K>?4fXzqZC*&cjEG$5Yw7;nWw z#IvEbgs_fR^a_JFOU&r_HHKPckla$R*}h0xhjP-)EgdF;D;^X|2@wT_k{%7AY@?mr z?$~-qpKw3&F0p=}WHMjgED&ApY3qFN0aQ7QG?%;)Z|HpWI5&KXC$tXE=CZu0zoe-4 z{%Au7SBATSY@V~UgYfjV*Pi-Vd5AastR8L6sI;rANP~OzoXwJ(Dla0Uh|!hb_Dk1xgS;v zJ3Bd+S2~$k-o=^rYqK>GXPWIp846^jpkM?~7)E{F()|FABsvVVaPg9O-K`Ts`BOWk z%qD9m%m&4jH+s)B#G>NK3ptq*wekdvCi(JBjqh5FGi4q8k3TcQjwffO#i5!zcwr%< z5W;tl2g;B4qlg^WYHlbOXBVcAXQ%|zw0L>>GtR!OkLp~P*Bds0z(Zv6Alx>VybV^trc zktct#0(vRcS)!hKYe z^iJ2t62`q#$`OW{XTnS0q$qchI1n}*+@{d!p>fO`-sq^0(CcLLk?v}E#M=Ue`96ur zHg`51!uto7CiBr`!bK)%9$TN3Ho_u38?ONLUmupsQ?EdJF~^lo+VJe&#mq$-wMdu% zM=y7>57oy7C=~v;`uLQLtB%uRr0$33^x~_`v%719&(0Ii9-ADkwAgB$9lgv@qU$We z-{$9>HqKVQucHcrug`CcpXFWj2p2vx^A%|Y!y2Yt?FSREN>t}D24U!Ln|Cvr)EV`Hk{!Jq){|d?fkWTy~ zDU$nFQWUh){zJzV6hhboj8m@%doEjHUL)eql9bey9Uc~gj{GKNeXob8y%zBlo*puu ztAKn&mLw5z&mZi|h9oAfyJhPF_>K&!-|!LzaH3Jcc_-076VHG3mja0(OWBhz8k5RS zONeNgj#s(odZ#|`KU&1kCgNl@h)lmg=Aix)L3LUW5#BctkRV795S)J^u&{In*gLuZ zJ0ohG@3cl2#)iChB!O9F6KQC`0{1?8BcNl$u8GYNflUO{yrQ_709M(!l0~QuL!f+t zk-(B_feY6#OVObRvEk12^-%<)ppe|aH8|eKv@0`sMy44>u9_S8@UdQfBQe=FX%&kY zZ+>%2lx)UI3ar+1`m7-LX5hgZAi1HKcPbPM0pcd+Q2ATWlprXQg(u6eKdVy-!<4c? zp5Sx932uyWp{;+Npq%&#$;fZ;=VD$`yolPx@*LOjS*>yzdk1=qBoemxd_z% zO2`Z|l6^NIb%c5mW|!#?iiM(oiQK>4t*5jWeR`zh*biiY9Um-ZDp}j`C@KL}jAKCr z$pHOO-ZBl{T5#(N9Xvgvd)9&btZUN3Q&c#rmbOs$p`iIvYek8GD+9%msVwE7!MkXl z1p=NVL%I$YJ8e%bLgXD8X0Z1dGI-!aedp+-&CW2j+K`$B9{Gue2{?aeTAY!_49PVK-W=#Z4lvb}!QRjKWb z0{+;#5e;Saz9`k~cJi>}yj(BWU*^oh4Hm&RONoOA{&Lvlo{y>I76;fX6Yq_Vqb>o{+CZw{T0&xX-ECTDTERGh>?SL+B;fI zL`ks!?Gz~?eQc=OO!T(j5!{l8g5i?yJ{jx#p(O3KW;R`IEZj2scpljFMMNSYgLkA~ zpflZkGxJrDC&V5~aztBME6#*-3kh3L_^&Dx<}zLKgD?t!#8FBce!WAE-_yO|m#=<@ z>MLb9%Tag7>`*ES{5ORE``>ije?qXeH<7monEqFt;J@|$>qqqs!X2Z3L9hNRWdEby ze`1djhV5fQ7rSCs7bBkV_?=)FnL#%6$E zgog-?Ro@-v{tnNT2fOiWy_cZ;vv;yJ zv@n>USeCB?lOi`g~v0pp&#SF89X8QX?n~H(`My~DVIh~CqSU2TPDI9RltKR=#OL5-YHR6Y`)}g2HZw3V_&xo@P^yh< zP-MmTQOF6HQLKq<8<&(rE6$HQKp2|Pnp_n9M07NQ9Q~vm-4A+tdidx+y2QViA-K+4 zwYfiLPFQdF?hqwN&zU*EU(qc5sACOnY|ISId-rvo;9-^g#{R5`0iVLfKSQ>E)xh< zsePRBafV+QV8FMSAAoqUL|hmx0?nPlWgV>a-B#ABIwop-hZtc`f`#q4*s(yMjasKm z@G!VTwyUURYW*6G>K~O!)<1LXEq|@|zm@>3zNFDpYH@O69&q+0BNZHnOV1t5z8t`C z{NwhA8=P(Uzr51DFn#(H)yhK?o~ZgxciML2+ASrIS<}rTcQ1SEeL1xv4`KkE)TRQ%djQMBPzUQB*nOfR!s zbKcB9ciwQA>Tl7w6K8eiw&Q6z*Og+cbTvNQw0P>IJ6Dr6WZN6txn-{tgF|-YJhl70 z^O*^Iul24As;^G8{^gs$OF93-#kSeA7aVHRVbFffm@H^)^q|(kUT*D_{&w*Nm!I>m ztFy1X-*K1w!Zp9zMHB4ux8L%*bN;BKmDamsd_1nqt^v#6{ykc@@Y%mjPdoeOeNnO% zn^Th~Ht+VfD7$;H5p9*X(>>--oOfkKe_L;p&VQy|7sJmxoImIIUT)vdKZmMXpDe$A z&f)thk@TBW?za?J`A3${c_P@~Tp4n<(nRu;!h@?e54#i2Y-aVolr`hs!Mi3i?<`uC zDJZy7$Vf%{rpJ<&4y8pQN3B*Swrq5(pRJdAa6Rq`s+r{)rk?Gkf|Bt@jyGQ8%qrWVuS+?NL6)y>3 zgx3HgTm^T8qomrcfv5cr8wdaw`yOwYHK}EiwvdoeleBots@}bsYpq_NDs%FlT7Ueu z+oOxhk5bN_%P6y{X4u|)tgzu1!>P#bCYFNx6U|@UeEsDE*DJBy1w0qrKJ`poz*bh1 z8TQ7;Y??wdU%CE^vw~MQyp23+;#<>xt;XltFa3AafG!6&^AQbvBFEFjx4|)OVVPsGM zlG;FwazYH2V_y&sftZFqa{)90YT63mVKVqkLrFpC#-dLlAdD^3Lo*g>LIK?r^qvF4 zl%rPIO~LCZ^kzKBILKi+?bc|M%H>^PBK-_f&r#Uzr21y&}tsI1bGwjhEhMeB Date: Mon, 20 Feb 2023 12:13:06 -0500 Subject: [PATCH 45/74] fixing event logs --- ClicksMigratingProblems/randomPunishments.js | 2 +- src/actions/createModActionTicket.ts | 4 +- src/actions/tickets/create.ts | 4 +- src/actions/tickets/delete.ts | 12 +++--- src/api/index.ts | 2 +- src/commands/mod/ban.ts | 4 +- src/commands/mod/kick.ts | 8 ++-- src/commands/mod/mute.ts | 18 ++++----- src/commands/mod/nick.ts | 4 +- src/commands/mod/purge.ts | 4 +- src/commands/mod/softban.ts | 4 +- src/commands/mod/unban.ts | 4 +- src/commands/mod/unmute.ts | 4 +- src/commands/mod/warn.ts | 2 +- src/commands/nucleus/ping.ts | 4 +- src/config/default.json | 2 +- src/context/messages/purgeto.ts | 2 +- src/events/channelCreate.ts | 5 ++- src/events/channelDelete.ts | 5 ++- src/events/channelUpdate.ts | 38 ++++++++++++++----- src/events/emojiCreate.ts | 5 ++- src/events/emojiDelete.ts | 3 +- src/events/emojiUpdate.ts | 3 +- src/events/guildBanAdd.ts | 9 +++-- src/events/guildBanRemove.ts | 11 +++--- src/events/guildMemberUpdate.ts | 21 +++++----- src/events/guildUpdate.ts | 7 ++-- src/events/inviteCreate.ts | 5 ++- src/events/inviteDelete.ts | 7 ++-- src/events/memberJoin.ts | 5 ++- src/events/memberLeave.ts | 40 +++++++++++++------- src/events/messageCreate.ts | 27 +++++++------ src/events/messageDelete.ts | 9 +++-- src/events/messageEdit.ts | 7 +++- src/events/roleCreate.ts | 3 +- src/events/roleDelete.ts | 5 ++- src/events/roleUpdate.ts | 6 +-- src/events/stickerCreate.ts | 3 +- src/events/stickerDelete.ts | 7 ++-- src/events/stickerUpdate.ts | 4 +- src/events/threadCreate.ts | 5 ++- src/events/threadDelete.ts | 7 ++-- src/events/threadUpdate.ts | 7 ++-- src/events/webhookUpdate.ts | 11 +++--- src/premium/createTranscript.ts | 4 +- src/utils/client.ts | 10 +---- src/utils/commandRegistration/register.ts | 2 +- src/utils/database.ts | 4 +- src/utils/eventScheduler.ts | 4 +- src/utils/log.ts | 39 +++++++++++++++---- src/utils/temp/generateFileName.ts | 2 +- 51 files changed, 244 insertions(+), 170 deletions(-) diff --git a/ClicksMigratingProblems/randomPunishments.js b/ClicksMigratingProblems/randomPunishments.js index af9c908..432a9f8 100644 --- a/ClicksMigratingProblems/randomPunishments.js +++ b/ClicksMigratingProblems/randomPunishments.js @@ -11,7 +11,7 @@ for (let i = 0; i < 100; i++) { Math.floor(Math.random() * 9) ]; // Select a random date in the last year - let date = new Date(new Date().getTime() - Math.floor(Math.random() * 31536000000)); + let date = new Date(Date.now() - Math.floor(Math.random() * 31536000000)); // Add to database await collection.insertOne({ type: type, diff --git a/src/actions/createModActionTicket.ts b/src/actions/createModActionTicket.ts index 24c0057..d86c14a 100644 --- a/src/actions/createModActionTicket.ts +++ b/src/actions/createModActionTicket.ts @@ -157,12 +157,12 @@ export async function create( calculateType: "ticketUpdate", color: NucleusColors.green, emoji: "GUILD.TICKET.OPEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { ticketFor: entry(user.id, renderUser(user)), createdBy: entry(createdBy.id, renderUser(createdBy)), - created: entry((new Date().getTime()).toString(), renderDelta(new Date().getTime())), + created: entry((Date.now()).toString(), renderDelta(Date.now())), ticketChannel: entry(c.id, renderChannel(c)) }, hidden: { diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts index 935f4ae..237790e 100644 --- a/src/actions/tickets/create.ts +++ b/src/actions/tickets/create.ts @@ -323,11 +323,11 @@ export default async function (interaction: CommandInteraction | ButtonInteracti calculateType: "ticketUpdate", color: NucleusColors.green, emoji: "GUILD.TICKET.OPEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { ticketFor: entry(interaction.member!.user.id, renderUser(interaction.member!.user! as Discord.User)), - created: entry(new Date().getTime(), renderDelta(new Date().getTime())), + created: entry(Date.now(), renderDelta(Date.now())), ticketChannel: entry(c.id, renderChannel(c)) }, hidden: { diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index fbfa221..a1f63d2 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -50,7 +50,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI calculateType: "ticketUpdate", color: NucleusColors.red, emoji: "GUILD.TICKET.CLOSE", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { ticketFor: entry( @@ -58,7 +58,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI renderUser((await interaction.guild.members.fetch(uID!)).user) ), closedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)), - closed: entry(new Date().getTime(), renderDelta(new Date().getTime())), + closed: entry(Date.now(), renderDelta(Date.now())), ticketChannel: entry(channel.id, channel.name) }, hidden: { @@ -120,7 +120,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI calculateType: "ticketUpdate", color: NucleusColors.yellow, emoji: "GUILD.TICKET.ARCHIVED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { ticketFor: entry( @@ -128,7 +128,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI renderUser((await interaction.guild.members.fetch(uID!)).user) ), archivedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)), - archived: entry(new Date().getTime(), renderDelta(new Date().getTime())), + archived: entry(Date.now(), renderDelta(Date.now())), ticketChannel: entry(channel.id, renderChannel(channel)) }, hidden: { @@ -183,12 +183,12 @@ async function purgeByUser(member: string, guild: string) { calculateType: "ticketUpdate", color: NucleusColors.red, emoji: "GUILD.TICKET.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { ticketFor: entry(member, renderUser(member)), deletedBy: entry(null, "Member left server"), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + deleted: entry(Date.now(), renderDelta(Date.now())), ticketsDeleted: deleted }, hidden: { diff --git a/src/api/index.ts b/src/api/index.ts index c24327d..bfe9d18 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -57,7 +57,7 @@ const runServer = (client: NucleusClient) => { calculateType: "guildMemberVerify", color: NucleusColors.green, emoji: "CONTROL.BLOCKTICK", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { member: entry(member.id, renderUser(member.user)), diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 847e66c..0ee663f 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -122,12 +122,12 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.red, emoji: "PUNISH.BAN.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.user.id, `\`${member.user.id}\``), name: entry(member.user.id, renderUser(member.user)), - banned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), + banned: entry(Date.now().toString(), renderDelta(Date.now())), bannedBy: entry(interaction.user.id, renderUser(interaction.user)), reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"), accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)), diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 45155aa..9f1ee41 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -101,8 +101,8 @@ const callback = async (interaction: CommandInteraction): Promise => { await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason); const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; const timeInServer = member.joinedTimestamp ? entry( - (new Date().getTime() - member.joinedTimestamp).toString(), - humanizeDuration(new Date().getTime() - member.joinedTimestamp, { + (Date.now() - member.joinedTimestamp).toString(), + humanizeDuration(Date.now() - member.joinedTimestamp, { round: true }) ) : entry(null, "*Unknown*") @@ -113,13 +113,13 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.red, emoji: "PUNISH.KICK.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.id, `\`${member.id}\``), name: entry(member.id, renderUser(member.user)), joined: undefined as (unknown | typeof entry), - kicked: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), + kicked: entry(Date.now().toString(), renderDelta(Date.now())), kickedBy: entry(interaction.user.id, renderUser(interaction.user)), reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"), timeInServer: timeInServer, diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index b8f592c..878d696 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -235,8 +235,8 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( `You have been muted in ${interaction.guild.name}` + (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") + "\n\n" + - `You will be unmuted at: at ` + - ` ( at ` + + ` ()` + "\n\n" + (createAppealTicket ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>` @@ -267,10 +267,10 @@ const callback = async (interaction: CommandInteraction): Promise => { await member.timeout(muteTime * 1000, reason || "*No reason provided*"); if (config.moderation.mute.role !== null) { await member.roles.add(config.moderation.mute.role); - await client.database.eventScheduler.schedule("naturalUnmute", (new Date().getTime() + muteTime * 1000).toString(), { + await client.database.eventScheduler.schedule("naturalUnmute", (Date.now() + muteTime * 1000).toString(), { guild: interaction.guild.id, user: member.id, - expires: new Date().getTime() + muteTime * 1000 + expires: Date.now() + muteTime * 1000 }); } } else { @@ -282,7 +282,7 @@ const callback = async (interaction: CommandInteraction): Promise => { try { if (config.moderation.mute.role !== null) { await member.roles.add(config.moderation.mute.role); - await client.database.eventScheduler.schedule("unmuteRole", (new Date().getTime() + muteTime * 1000).toString(), { + await client.database.eventScheduler.schedule("unmuteRole", (Date.now() + muteTime * 1000).toString(), { guild: interaction.guild.id, user: member.id, role: config.moderation.mute.role @@ -325,16 +325,16 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.yellow, emoji: "PUNISH.WARN.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.user.id, `\`${member.user.id}\``), name: entry(member.user.id, renderUser(member.user)), mutedUntil: entry( - (new Date().getTime() + muteTime * 1000).toString(), - renderDelta(new Date().getTime() + muteTime * 1000) + (Date.now() + muteTime * 1000).toString(), + renderDelta(Date.now() + muteTime * 1000) ), - muted: entry(new Date().getTime.toString(), renderDelta(new Date().getTime() - 1000)), + muted: entry(new Date().getTime.toString(), renderDelta(Date.now() - 1000)), mutedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)), reason: entry(reason, reason ? reason : "*No reason provided*") }, diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 792e535..8fb1cc1 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -156,13 +156,13 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberUpdate", color: NucleusColors.yellow, emoji: "PUNISH.NICKNAME.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.id, `\`${member.id}\``), before: entry(before, before ?? "*No nickname set*"), after: entry(nickname ?? null, nickname ?? "*No nickname set*"), - updated: entry(new Date().getTime(), renderDelta(new Date().getTime())), + updated: entry(Date.now(), renderDelta(Date.now())), updatedBy: entry(interaction.user.id, renderUser(interaction.user)) }, hidden: { diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 6673c97..cabcb1e 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -148,7 +148,7 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "messageDelete", color: NucleusColors.red, emoji: "CHANNEL.PURGE.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), @@ -296,7 +296,7 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "messageDelete", color: NucleusColors.red, emoji: "CHANNEL.PURGE.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index d6ef481..fa9f11b 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -124,12 +124,12 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.yellow, emoji: "PUNISH.BAN.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.user.id, `\`${member.user.id}\``), name: entry(member.user.id, renderUser(member.user)), - softbanned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), + softbanned: entry(Date.now().toString(), renderDelta(Date.now())), softbannedBy: entry(interaction.user.id, renderUser(interaction.user)), reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"), accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)), diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts index ac4823e..40f4504 100644 --- a/src/commands/mod/unban.ts +++ b/src/commands/mod/unban.ts @@ -57,12 +57,12 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.green, emoji: "PUNISH.BAN.GREEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.id, `\`${member.id}\``), name: entry(member.id, renderUser(member)), - unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())), + unbanned: entry(Date.now(), renderDelta(Date.now())), unbannedBy: entry(interaction.user.id, renderUser(interaction.user)), accountCreated: entry(member.createdTimestamp, renderDelta(member.createdTimestamp)) }, diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts index 826ed5c..44f807d 100644 --- a/src/commands/mod/unmute.ts +++ b/src/commands/mod/unmute.ts @@ -105,12 +105,12 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.green, emoji: "PUNISH.MUTE.GREEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.user.id, `\`${member.user.id}\``), name: entry(member.user.id, renderUser(member.user)), - unmuted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), + unmuted: entry(Date.now().toString(), renderDelta(Date.now())), unmutedBy: entry(interaction.user.id, renderUser(interaction.user)) }, hidden: { diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index ca3bfc0..35b5400 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -116,7 +116,7 @@ const callback = async (interaction: CommandInteraction): Promise => { calculateType: "guildMemberPunish", color: NucleusColors.yellow, emoji: "PUNISH.WARN.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { user: entry( diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts index 1107f34..3e02a8f 100644 --- a/src/commands/nucleus/ping.ts +++ b/src/commands/nucleus/ping.ts @@ -10,9 +10,9 @@ const command = (builder: SlashCommandSubcommandBuilder) => const callback = async (interaction: CommandInteraction): Promise => { // WEBSOCKET | Nucleus -> Discord // EDITING | Nucleus -> discord -> nucleus | edit time / 2 - const initial = new Date().getTime(); + const initial = Date.now(); await interaction.reply({ embeds: LoadingEmbed, ephemeral: true }); - const ping = new Date().getTime() - initial; + const ping = Date.now() - initial; interaction.editReply({ embeds: [ new EmojiEmbed() diff --git a/src/config/default.json b/src/config/default.json index 858a835..704896f 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -107,7 +107,7 @@ "text": null, "link": null }, - "nickname": { + "nick": { "text": null, "link": null } diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts index 8e2cf92..b0211ec 100644 --- a/src/context/messages/purgeto.ts +++ b/src/context/messages/purgeto.ts @@ -171,7 +171,7 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { calculateType: "messageDelete", color: NucleusColors.red, emoji: "PUNISH.BAN.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts index dda37af..b42ded7 100644 --- a/src/events/channelCreate.ts +++ b/src/events/channelCreate.ts @@ -4,7 +4,8 @@ import type { NucleusClient } from "../utils/client.js"; export const event = "channelCreate"; export async function callback(client: NucleusClient, channel: GuildBasedChannel) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(channel.guild.id, "channelUpdate")) return; const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0]; if (!auditLog) return; @@ -62,7 +63,7 @@ export async function callback(client: NucleusClient, channel: GuildBasedChannel calculateType: "channelUpdate", color: NucleusColors.green, emoji: emoji, - timestamp: channel.createdTimestamp + timestamp: channel.createdTimestamp ?? Date.now() }, list: { channelId: entry(channel.id, `\`${channel.id}\``), diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts index a93e6c7..890a15f 100644 --- a/src/events/channelDelete.ts +++ b/src/events/channelDelete.ts @@ -14,7 +14,8 @@ import getEmojiByName from "../utils/getEmojiByName.js"; export const event = "channelDelete"; export async function callback(client: NucleusClient, channel: GuildBasedChannel) { - const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = client.logger; + const { getAuditLog, log, isLogging, NucleusColors, entry, renderDelta, renderUser } = client.logger; + if (!await isLogging(channel.guild.id, "channelUpdate")) return; const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0]; if (!auditLog) return; @@ -81,7 +82,7 @@ export async function callback(client: NucleusClient, channel: GuildBasedChannel ), nsfw: null, created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp!)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + deleted: entry(Date.now(), renderDelta(Date.now())), deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)) }; if ((channel instanceof BaseGuildTextChannel || channel instanceof StageChannel) && channel.topic !== null) diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts index 6c8687e..052acc1 100644 --- a/src/events/channelUpdate.ts +++ b/src/events/channelUpdate.ts @@ -32,11 +32,11 @@ interface channelChanges { topic?: ReturnType; bitrate?: ReturnType; userLimit?: ReturnType; - rateLimitPerUser?: ReturnType; parent?: ReturnType; permissionOverwrites?: ReturnType; region?: ReturnType; maxUsers?: ReturnType; + autoArchiveDuration?: ReturnType; } @@ -44,8 +44,9 @@ interface channelChanges { export const event = "channelUpdate"; export async function callback(client: NucleusClient, oldChannel: GuildChannel, newChannel: GuildChannel) { + const { getAuditLog, log, isLogging, NucleusColors, renderDelta, renderUser, renderChannel } = client.logger; + if (!await isLogging(newChannel.guild.id, "channelUpdate")) return; const config = await client.memory.readGuildInfo(newChannel.guild.id); - const { getAuditLog, log, NucleusColors, renderDelta, renderUser, renderChannel } = client.logger; entry = client.logger.entry; if (newChannel.parent && newChannel.parent.id === config.tickets.category) return; @@ -60,7 +61,7 @@ export async function callback(client: NucleusClient, oldChannel: GuildChannel, const changes: channelChanges = { channelId: entry(newChannel.id, `\`${newChannel.id}\``), channel: entry(newChannel.id, renderChannel(newChannel)), - edited: entry(new Date().getTime(), renderDelta(new Date().getTime())), + edited: entry(Date.now(), renderDelta(Date.now())), editedBy: entry(auditLog.executor!.id, renderUser((await newChannel.guild.members.fetch(auditLog.executor!.id)).user)), }; if (oldChannel.name !== newChannel.name) changes.name = entry([oldChannel.name, newChannel.name], `${oldChannel.name} -> ${newChannel.name}`); @@ -68,12 +69,16 @@ export async function callback(client: NucleusClient, oldChannel: GuildChannel, changes.position = entry([oldChannel.position.toString(), newChannel.position.toString()], `${oldChannel.position} -> ${newChannel.position}`); switch (newChannel.type) { + case ChannelType.PrivateThread: + case ChannelType.PublicThread: { + return; + } case ChannelType.GuildText: { emoji = "CHANNEL.TEXT.EDIT"; readableType = "Text"; displayName = "Text Channel"; - let oldTopic = (oldChannel as TextChannel).topic, - newTopic = (newChannel as TextChannel).topic; + let oldTopic = (oldChannel as TextChannel).topic ?? "*None*", + newTopic = (oldChannel as TextChannel).topic ?? "*None*"; if (oldTopic) { if (oldTopic.length > 256) oldTopic = `\`\`\`\n${oldTopic.replace("`", "'").substring(0, 253) + "..."}\n\`\`\``; @@ -91,14 +96,20 @@ export async function callback(client: NucleusClient, oldChannel: GuildChannel, const nsfw = ["", ""]; nsfw[0] = (oldChannel as TextChannel).nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`; nsfw[1] = (newChannel as TextChannel).nsfw ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`; - if ((oldChannel as TextChannel).topic !== (newChannel as TextChannel).topic) + if (oldTopic !== newTopic) changes.description = entry([(oldChannel as TextChannel).topic ?? "", (newChannel as TextChannel).topic ?? ""], `\nBefore: ${oldTopic}\nAfter: ${newTopic}`); if ((oldChannel as TextChannel).nsfw !== (newChannel as TextChannel).nsfw) changes.nsfw = entry([(oldChannel as TextChannel).nsfw ? "On" : "Off", (newChannel as TextChannel).nsfw ? "On" : "Off"], `${nsfw[0]} -> ${nsfw[1]}`); - if ((oldChannel as TextChannel).rateLimitPerUser !== (newChannel as TextChannel).rateLimitPerUser && (oldChannel as TextChannel).rateLimitPerUser !== 0) - changes.rateLimitPerUser = entry( + if ((oldChannel as TextChannel).rateLimitPerUser !== (newChannel as TextChannel).rateLimitPerUser) + changes.slowmode = entry( [((oldChannel as TextChannel).rateLimitPerUser).toString(), ((newChannel as TextChannel).rateLimitPerUser).toString()], `${humanizeDuration((oldChannel as TextChannel).rateLimitPerUser * 1000)} -> ${humanizeDuration((newChannel as TextChannel).rateLimitPerUser * 1000)}` ); + if((oldChannel as TextChannel).defaultAutoArchiveDuration !== (newChannel as TextChannel).defaultAutoArchiveDuration) { + changes.autoArchiveDuration = entry( + [((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString(), ((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString()], + `${humanizeDuration(((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)} -> ${humanizeDuration(((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)}` + ); + } break; } @@ -122,8 +133,15 @@ export async function callback(client: NucleusClient, oldChannel: GuildChannel, } else { newTopic = "None"; } - if ((oldChannel as TextChannel).nsfw !== (newChannel as TextChannel).nsfw) + if ((oldChannel as TextChannel).nsfw !== (newChannel as TextChannel).nsfw) { changes.nsfw = entry([(oldChannel as TextChannel).nsfw ? "On" : "Off", (newChannel as TextChannel).nsfw ? "On" : "Off"], `${(oldChannel as TextChannel).nsfw ? "On" : "Off"} -> ${(newChannel as TextChannel).nsfw ? "On" : "Off"}`); + } + if((oldChannel as TextChannel).defaultAutoArchiveDuration !== (newChannel as TextChannel).defaultAutoArchiveDuration) { + changes.autoArchiveDuration = entry( + [((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString(), ((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320).toString()], + `${humanizeDuration(((oldChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)} -> ${humanizeDuration(((newChannel as TextChannel).defaultAutoArchiveDuration ?? 4320) * 60 * 1000)}` + ); + } break; } case ChannelType.GuildVoice: { @@ -174,7 +192,7 @@ export async function callback(client: NucleusClient, oldChannel: GuildChannel, if ((oldChannel as StageChannel).rtcRegion !== (newChannel as StageChannel).rtcRegion) changes.region = entry( [(oldChannel as StageChannel).rtcRegion ?? "Automatic", (newChannel as StageChannel).rtcRegion ?? "Automatic"], - `${capitalize((oldChannel as StageChannel).rtcRegion?.toUpperCase() ?? "automatic")} -> ${capitalize((newChannel as StageChannel).rtcRegion?.toUpperCase() ?? "automatic")}` + `${capitalize((oldChannel as StageChannel).rtcRegion?.toLowerCase() ?? "automatic")} -> ${capitalize((newChannel as StageChannel).rtcRegion?.toLowerCase() ?? "automatic")}` ); break; } diff --git a/src/events/emojiCreate.ts b/src/events/emojiCreate.ts index 8023abc..2630295 100644 --- a/src/events/emojiCreate.ts +++ b/src/events/emojiCreate.ts @@ -4,9 +4,10 @@ import type { GuildEmoji, GuildAuditLogsEntry } from 'discord.js' export const event = "emojiCreate"; export async function callback(client: NucleusClient, emoji: GuildEmoji) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; + const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; + if (!await isLogging(emoji.guild.id, "emojiUpdate")) return; const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate)) - .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0]; + .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0]; if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; const data = { diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts index f607cf4..f3c58fa 100644 --- a/src/events/emojiDelete.ts +++ b/src/events/emojiDelete.ts @@ -4,7 +4,8 @@ import type { GuildEmoji, GuildAuditLogsEntry } from 'discord.js' export const event = "emojiDelete"; export async function callback(client: NucleusClient, emoji: GuildEmoji) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; + const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; + if (!await isLogging(emoji.guild.id, "emojiUpdate")) return; const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0]; if (!auditLog) return; diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts index 201dd42..59b4488 100644 --- a/src/events/emojiUpdate.ts +++ b/src/events/emojiUpdate.ts @@ -4,7 +4,8 @@ import type { GuildEmoji, GuildAuditLogsEntry } from 'discord.js' export const event = "emojiUpdate"; export async function callback(client: NucleusClient, oldEmoji: GuildEmoji, newEmoji: GuildEmoji) { - const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderEmoji } = client.logger; + const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; + if (!(await isLogging(newEmoji.guild.id, "emojiUpdate"))) return; const auditLog = (await getAuditLog(newEmoji.guild, AuditLogEvent.EmojiCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === newEmoji.id)[0]; diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts index 3d96245..cac4b41 100644 --- a/src/events/guildBanAdd.ts +++ b/src/events/guildBanAdd.ts @@ -7,10 +7,11 @@ import type { NucleusClient } from "../utils/client.js"; export const event = "guildBanAdd"; export async function callback(client: NucleusClient, ban: GuildBan) { + const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; await statsChannelRemove(client, undefined, ban.guild, ban.user); purgeByUser(ban.user.id, ban.guild.id); - const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; - const auditLog: GuildAuditLogsEntry | undefined = (await getAuditLog(ban.guild, AuditLogEvent.EmojiCreate)) + if (!(await isLogging(ban.guild.id, "guildMemberPunish"))) return; + const auditLog: GuildAuditLogsEntry | undefined = (await getAuditLog(ban.guild, AuditLogEvent.MemberBanAdd)) .filter((entry: GuildAuditLogsEntry) => ((entry.target! as User).id === ban.user.id))[0]; if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; @@ -22,12 +23,12 @@ export async function callback(client: NucleusClient, ban: GuildBan) { calculateType: "guildMemberPunish", color: NucleusColors.red, emoji: "PUNISH.BAN.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(ban.user.id, `\`${ban.user.id}\``), name: entry(ban.user.id, renderUser(ban.user)), - banned: entry(new Date().getTime(), renderDelta(new Date().getTime())), + banned: entry(Date.now(), renderDelta(Date.now())), bannedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), reason: entry(auditLog.reason, auditLog.reason ? `\n> ${auditLog.reason}` : "*No reason provided.*"), accountCreated: entry(ban.user.createdTimestamp, renderDelta(ban.user.createdTimestamp)), diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts index bcb70d5..3be4560 100644 --- a/src/events/guildBanRemove.ts +++ b/src/events/guildBanRemove.ts @@ -1,14 +1,13 @@ import type { GuildAuditLogsEntry, GuildBan, User } from "discord.js"; import { AuditLogEvent } from "discord.js"; -import { purgeByUser } from "../actions/tickets/delete.js"; import type { NucleusClient } from "../utils/client.js"; export const event = "guildBanRemove"; export async function callback(client: NucleusClient, ban: GuildBan) { - purgeByUser(ban.user.id, ban.guild.id); - const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; - const auditLog = (await getAuditLog(ban.guild, AuditLogEvent.EmojiCreate)) + const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; + if (!await isLogging(ban.guild.id, "guildMemberPunish")) return; + const auditLog = (await getAuditLog(ban.guild, AuditLogEvent.MemberBanRemove)) .filter((entry: GuildAuditLogsEntry) => ((entry.target! as User).id === ban.user.id))[0]; if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; @@ -20,12 +19,12 @@ export async function callback(client: NucleusClient, ban: GuildBan) { calculateType: "guildMemberPunish", color: NucleusColors.green, emoji: "PUNISH.BAN.GREEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(ban.user.id, `\`${ban.user.id}\``), name: entry(ban.user.id, renderUser(ban.user)), - unbanned: entry(new Date().getTime(), renderDelta(new Date().getTime())), + unbanned: entry(Date.now(), renderDelta(Date.now())), unbannedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), accountCreated: entry(ban.user.createdTimestamp, renderDelta(ban.user.createdTimestamp)) }, diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index d25c9c4..81b2337 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -4,7 +4,8 @@ import type { NucleusClient } from "../utils/client.js"; export const event = "guildMemberUpdate"; export async function callback(client: NucleusClient, before: GuildMember, after: GuildMember) { - const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; + const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; + if (!await isLogging(after.guild.id, "memberUpdate")) return; const auditLog = (await getAuditLog(after.guild, AuditLogEvent.EmojiCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0]; if (!auditLog) return; @@ -26,14 +27,14 @@ export async function callback(client: NucleusClient, before: GuildMember, after calculateType: "guildMemberUpdate", color: NucleusColors.yellow, emoji: "PUNISH.NICKNAME.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(after.id, `\`${after.id}\``), name: entry(after.user.id, renderUser(after.user)), before: entry(before.nickname, before.nickname ? before.nickname : "*None*"), after: entry(after.nickname, after.nickname ? after.nickname : "*None*"), - changed: entry(new Date().getTime(), renderDelta(new Date().getTime())), + changed: entry(Date.now(), renderDelta(Date.now())), changedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)) }, hidden: { @@ -42,8 +43,8 @@ export async function callback(client: NucleusClient, before: GuildMember, after }; log(data); } else if ( - (before.communicationDisabledUntilTimestamp ?? 0) < new Date().getTime() && - (after.communicationDisabledUntil ?? 0) > new Date().getTime() + (before.communicationDisabledUntilTimestamp ?? 0) < Date.now() && + (after.communicationDisabledUntil ?? 0) > Date.now() ) { await client.database.history.create( "mute", @@ -62,7 +63,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after calculateType: "guildMemberPunish", color: NucleusColors.yellow, emoji: "PUNISH.MUTE.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(after.id, `\`${after.id}\``), @@ -71,7 +72,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after after.communicationDisabledUntilTimestamp, renderDelta(after.communicationDisabledUntilTimestamp!) ), - muted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + muted: entry(Date.now(), renderDelta(Date.now())), mutedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), reason: entry(auditLog.reason, auditLog.reason ? auditLog.reason : "\n> *No reason provided*") }, @@ -88,7 +89,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after } else if ( after.communicationDisabledUntil === null && before.communicationDisabledUntilTimestamp !== null && - new Date().getTime() >= auditLog.createdTimestamp + Date.now() >= auditLog.createdTimestamp ) { await client.database.history.create( "unmute", @@ -107,12 +108,12 @@ export async function callback(client: NucleusClient, before: GuildMember, after calculateType: "guildMemberPunish", color: NucleusColors.green, emoji: "PUNISH.MUTE.GREEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(after.id, `\`${after.id}\``), name: entry(after.user.id, renderUser(after.user)), - unmuted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + unmuted: entry(Date.now(), renderDelta(Date.now())), unmutedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)) }, hidden: { diff --git a/src/events/guildUpdate.ts b/src/events/guildUpdate.ts index 8690af2..6b25e48 100644 --- a/src/events/guildUpdate.ts +++ b/src/events/guildUpdate.ts @@ -6,7 +6,8 @@ export const event = "guildUpdate"; export async function callback(client: NucleusClient, before: Guild, after: Guild) { await statsChannelUpdate(client, after.members.me!); - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + if (!await isLogging(after.id, "guildUpdate")) return; const auditLog = (await getAuditLog(after, AuditLogEvent.GuildUpdate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Guild)!.id === after.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -74,7 +75,7 @@ export async function callback(client: NucleusClient, before: Guild, after: Guil ); if (!Object.keys(list).length) return; - list["updated"] = entry(new Date().getTime(), renderDelta(new Date().getTime())); + list["updated"] = entry(Date.now(), renderDelta(Date.now())); list["updatedBy"] = entry(auditLog.executor!.id, renderUser(auditLog.executor!)); const data = { meta: { @@ -83,7 +84,7 @@ export async function callback(client: NucleusClient, before: Guild, after: Guil calculateType: "guildUpdate", color: NucleusColors.yellow, emoji: "GUILD.YELLOW", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: list, hidden: { diff --git a/src/events/inviteCreate.ts b/src/events/inviteCreate.ts index a267f09..34f66dc 100644 --- a/src/events/inviteCreate.ts +++ b/src/events/inviteCreate.ts @@ -7,7 +7,8 @@ export const event = "inviteCreate"; export async function callback(client: NucleusClient, invite: Invite) { if(!invite.guild) return; // This is a DM invite (not a guild invite - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(invite.guild.id, "guildUpdate")) return; const auditLog = (await getAuditLog(invite.guild as Guild, AuditLogEvent.InviteCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Invite)!.code === invite.code)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -18,7 +19,7 @@ export async function callback(client: NucleusClient, invite: Invite) { calculateType: "guildUpdate", color: NucleusColors.green, emoji: "INVITE.CREATE", - timestamp: invite.createdTimestamp + timestamp: invite.createdTimestamp ?? Date.now() }, list: { channel: entry(invite.channel!.id, renderChannel(invite.channel as GuildChannel)), diff --git a/src/events/inviteDelete.ts b/src/events/inviteDelete.ts index 1ded432..456af90 100644 --- a/src/events/inviteDelete.ts +++ b/src/events/inviteDelete.ts @@ -7,7 +7,8 @@ export const event = "inviteDelete"; export async function callback(client: NucleusClient, invite: Invite) { if(!invite.guild) return; // This is a DM invite (not a guild invite - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(invite.guild.id, "guildUpdate")) return; const auditLog = (await getAuditLog(invite.guild as Guild, AuditLogEvent.InviteDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Invite)!.code === invite.code)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -18,14 +19,14 @@ export async function callback(client: NucleusClient, invite: Invite) { calculateType: "guildUpdate", color: NucleusColors.red, emoji: "INVITE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { channel: entry(invite.channel!.id, renderChannel(invite.channel as GuildChannel)), link: entry(invite.url, invite.url), expires: entry(invite.maxAge, invite.maxAge ? humanizeDuration(invite.maxAge * 1000) : "Never"), deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())) + deleted: entry(Date.now(), renderDelta(Date.now())) }, hidden: { guild: invite.guild!.id diff --git a/src/events/memberJoin.ts b/src/events/memberJoin.ts index daf195a..77b111f 100644 --- a/src/events/memberJoin.ts +++ b/src/events/memberJoin.ts @@ -8,7 +8,8 @@ export const event = "guildMemberAdd"; export async function callback(client: NucleusClient, member: GuildMember) { welcome(client, member); statsChannelAdd(client, member); - const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const { log, isLogging, NucleusColors, entry, renderUser, renderDelta } = client.logger; + if (!await isLogging(member.guild.id, "guildMemberUpdate")) return; await client.database.history.create("join", member.guild.id, member.user, null, null); const data = { meta: { @@ -17,7 +18,7 @@ export async function callback(client: NucleusClient, member: GuildMember) { calculateType: "guildMemberUpdate", color: NucleusColors.green, emoji: "MEMBER" + (member.user.bot ? ".BOT" : "") + ".JOIN", - timestamp: member.joinedTimestamp + timestamp: member.joinedTimestamp ?? Date.now() }, list: { memberId: entry(member.id, `\`${member.id}\``), diff --git a/src/events/memberLeave.ts b/src/events/memberLeave.ts index e70fb3c..8b3d1b1 100644 --- a/src/events/memberLeave.ts +++ b/src/events/memberLeave.ts @@ -7,22 +7,36 @@ import { callback as statsChannelRemove } from "../reflex/statsChannelUpdate.js" export const event = "guildMemberRemove"; export async function callback(client: NucleusClient, member: GuildMember) { + const startTime = Date.now() - 10 * 1000; purgeByUser(member.id, member.guild.id); await statsChannelRemove(client, member); - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; - const auditLog = (await getAuditLog(member.guild as Guild, AuditLogEvent.MemberKick)) + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + if (!await isLogging(member.guild.id, "guildMemberUpdate")) return; + const kickAuditLog = (await getAuditLog(member.guild as Guild, AuditLogEvent.MemberKick)) + .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === member.id)[0]; + const banAuditLog = (await getAuditLog(member.guild as Guild, AuditLogEvent.MemberBanAdd)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === member.id)[0]; let type = "leave"; - if (auditLog) { - if (auditLog.executor!.id === client.user!.id) return; - if (auditLog.createdAt.valueOf() - 100 >= new Date().getTime()) { + if (kickAuditLog) { + if (kickAuditLog.executor!.id === client.user!.id) return; + if (kickAuditLog.createdAt.getTime() >= startTime) { type = "kick"; } } + if (banAuditLog) { + if (banAuditLog.executor!.id === client.user!.id) return; + if (banAuditLog.createdAt.getTime() >= startTime) { + if (!kickAuditLog) { + return + } else if (kickAuditLog.createdAt.valueOf() < banAuditLog.createdAt.valueOf()) { + return + } + } + } let data; if (type === "kick") { - if (!auditLog) return; - await client.database.history.create("kick", member.guild.id, member.user, auditLog.executor, auditLog.reason); + if (!kickAuditLog) return; + await client.database.history.create("kick", member.guild.id, member.user, kickAuditLog.executor, kickAuditLog.reason); data = { meta: { type: "memberKick", @@ -30,15 +44,15 @@ export async function callback(client: NucleusClient, member: GuildMember) { calculateType: "guildMemberPunish", color: NucleusColors.red, emoji: "PUNISH.KICK.RED", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.id, `\`${member.id}\``), name: entry(member.id, renderUser(member.user)), joined: entry(member.joinedTimestamp, renderDelta(member.joinedTimestamp?.valueOf()!)), - kicked: entry(new Date().getTime(), renderDelta(new Date().getTime())), - kickedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), - reason: entry(auditLog.reason, auditLog.reason ? `\n> ${auditLog.reason}` : "*No reason provided.*"), + kicked: entry(Date.now(), renderDelta(Date.now())), + kickedBy: entry(kickAuditLog.executor!.id, renderUser(kickAuditLog.executor!)), + reason: entry(kickAuditLog.reason, kickAuditLog.reason ? `\n> ${kickAuditLog.reason}` : "*No reason provided.*"), accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)), serverMemberCount: member.guild.memberCount }, @@ -55,13 +69,13 @@ export async function callback(client: NucleusClient, member: GuildMember) { calculateType: "guildMemberUpdate", color: NucleusColors.red, emoji: "MEMBER." + (member.user.bot ? "BOT." : "") + "LEAVE", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(member.id, `\`${member.id}\``), name: entry(member.id, renderUser(member.user)), joined: entry(member.joinedTimestamp, renderDelta(member.joinedTimestamp?.valueOf()!)), - left: entry(new Date().getTime(), renderDelta(new Date().getTime())), + left: entry(Date.now(), renderDelta(Date.now())), accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)), serverMemberCount: member.guild.memberCount }, diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 0a62019..7f8e5d1 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -19,7 +19,7 @@ export async function callback(_client: NucleusClient, message: Message) { console.log(e); } - const { log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; const fileNames = await logAttachment(message); @@ -45,7 +45,7 @@ export async function callback(_client: NucleusClient, message: Message) { messageId: entry(message.id, `\`${message.id}\``), sentBy: entry(message.author.id, renderUser(message.author)), sentIn: entry(message.channel.id, renderChannel(message.channel)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + deleted: entry(Date.now(), renderDelta(Date.now())), mentions: message.mentions.users.size, attachments: entry(message.attachments.size, message.attachments.size + attachmentJump), repliedTo: entry( @@ -68,7 +68,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -105,7 +105,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -140,7 +140,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -170,7 +170,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -201,7 +201,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -233,7 +233,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -265,7 +265,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "autoModeratorDeleted", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: @@ -283,6 +283,7 @@ export async function callback(_client: NucleusClient, message: Message) { } if (config.filters.pings.everyone && message.mentions.everyone) { + if(!await isLogging(message.guild.id, "messageMassPing")) return; const data = { meta: { type: "everyonePing", @@ -290,7 +291,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "messageMassPing", color: NucleusColors.yellow, emoji: "MESSAGE.PING.EVERYONE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*" @@ -307,6 +308,7 @@ export async function callback(_client: NucleusClient, message: Message) { if (!config.filters.pings.allowed.roles.includes(roleId)) { messageException(message.guild.id, message.channel.id, message.id); await message.delete(); + if(!await isLogging(message.guild.id, "messageMassPing")) return; const data = { meta: { type: "rolePing", @@ -314,7 +316,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "messageMassPing", color: NucleusColors.yellow, emoji: "MESSAGE.PING.ROLE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: content @@ -333,6 +335,7 @@ export async function callback(_client: NucleusClient, message: Message) { if (message.mentions.users.size >= config.filters.pings.mass && config.filters.pings.mass) { messageException(message.guild.id, message.channel.id, message.id); await message.delete(); + if(!await isLogging(message.guild.id, "messageMassPing")) return; const data = { meta: { type: "massPing", @@ -340,7 +343,7 @@ export async function callback(_client: NucleusClient, message: Message) { calculateType: "messageMassPing", color: NucleusColors.yellow, emoji: "MESSAGE.PING.MASS", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*" diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index 65e1422..aac83f4 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -7,11 +7,12 @@ export async function callback(client: NucleusClient, message: Message) { if (message.author.id === client.user!.id) return; if (message.author.bot) return; if (client.noLog.includes(`${message.guild!.id}/${message.channel.id}/${message.id}`)) return; - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(message.guild!.id, "messageDelete")) return; const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd)) .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0]; if (auditLog) { - if (auditLog.createdTimestamp - 1000 < new Date().getTime()) return; + if (auditLog.createdTimestamp - 1000 < Date.now()) return; } const replyTo = message.reference; let content = message.cleanContent; @@ -35,7 +36,7 @@ export async function callback(client: NucleusClient, message: Message) { calculateType: "messageDelete", color: NucleusColors.red, emoji: "MESSAGE.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, separate: { start: content ? `**Message:**\n\`\`\`${content}\`\`\`` : "**Message:** *Message had no content*" @@ -44,7 +45,7 @@ export async function callback(client: NucleusClient, message: Message) { messageId: entry(message.id, `\`${message.id}\``), sentBy: entry(message.author.id, renderUser(message.author)), sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + deleted: entry(Date.now(), renderDelta(Date.now())), mentions: message.mentions.users.size, attachments: entry(attachments, attachments + attachmentJump), repliedTo: entry( diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts index d491641..f5a28a4 100644 --- a/src/events/messageEdit.ts +++ b/src/events/messageEdit.ts @@ -8,7 +8,8 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe if (newMessage.author.id === client.user!.id) return; if (newMessage.author.bot) return; if (!newMessage.guild) return; - const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger; + const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger; + const replyTo: MessageReference | null = newMessage.reference; let newContent = newMessage.cleanContent.replaceAll("`", "‘"); let oldContent = oldMessage.cleanContent.replaceAll("`", "‘"); @@ -20,6 +21,7 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe attachmentJump = ` [[View attachments]](${config})`; } if (newMessage.crosspostable !== oldMessage.crosspostable) { + if(!await isLogging(newMessage.guild.id, "messageAnnounce")) return; if (!replyTo) { const data = { meta: { @@ -28,7 +30,7 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe calculateType: "messageAnnounce", color: NucleusColors.yellow, emoji: "MESSAGE.CREATE", - timestamp: newMessage.editedTimestamp + timestamp: newMessage.editedTimestamp ?? Date.now() }, separate: { end: `[[Jump to message]](${newMessage.url})` @@ -58,6 +60,7 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe return log(data); } } + if (!await isLogging(newMessage.guild.id, "messageUpdate")) return; if (!newMessage.editedTimestamp) { return; } diff --git a/src/events/roleCreate.ts b/src/events/roleCreate.ts index d253ce7..be385f0 100644 --- a/src/events/roleCreate.ts +++ b/src/events/roleCreate.ts @@ -4,7 +4,8 @@ import { AuditLogEvent, Guild, GuildAuditLogsEntry, Role } from "discord.js"; export const event = "roleCreate"; export async function callback(client: NucleusClient, role: Role) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderRole } = client.logger; + if (!await isLogging(role.guild.id, "guildRoleUpdate")) return; if (role.managed) return; const auditLog = (await getAuditLog(role.guild as Guild, AuditLogEvent.RoleCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === role.id)[0]!; diff --git a/src/events/roleDelete.ts b/src/events/roleDelete.ts index f41241b..b207f4f 100644 --- a/src/events/roleDelete.ts +++ b/src/events/roleDelete.ts @@ -5,7 +5,8 @@ import { AuditLogEvent, Guild, GuildAuditLogsEntry, Role } from "discord.js"; export const event = "roleDelete"; export async function callback(client: NucleusClient, role: Role) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + if (!await isLogging(role.guild.id, "guildRoleUpdate")) return; if (role.managed) return; const auditLog = (await getAuditLog(role.guild as Guild, AuditLogEvent.RoleDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === role.id)[0]!; @@ -34,7 +35,7 @@ export async function callback(client: NucleusClient, role: Role) { members: entry(role.members.size, `${role.members.size}`), deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), created: entry(role.createdTimestamp, renderDelta(role.createdTimestamp)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())) + deleted: entry(Date.now(), renderDelta(Date.now())) }, hidden: { guild: role.guild.id diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts index 09e062f..8d9ef10 100644 --- a/src/events/roleUpdate.ts +++ b/src/events/roleUpdate.ts @@ -5,8 +5,8 @@ import getEmojiByName from "../utils/getEmojiByName.js"; export const event = "roleUpdate"; export async function callback(client: NucleusClient, oldRole: Role, newRole: Role) { - const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger; - + const { getAuditLog, isLogging, log, NucleusColors, entry, renderDelta, renderUser, renderRole } = client.logger; + if (!await isLogging(newRole.guild.id, "guildRoleUpdate")) return; const auditLog = (await getAuditLog(newRole.guild as Guild, AuditLogEvent.RoleUpdate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Role)!.id === newRole.id)[0]; if (!auditLog) return; @@ -15,7 +15,7 @@ export async function callback(client: NucleusClient, oldRole: Role, newRole: Ro const changes: Record> = { roleId: entry(newRole.id, `\`${newRole.id}\``), role: entry(newRole.id, renderRole(newRole)), - edited: entry(new Date().getTime(), renderDelta(new Date().getTime())), + edited: entry(Date.now(), renderDelta(Date.now())), editedBy: entry(auditLog.executor!.id, renderUser((await newRole.guild.members.fetch(auditLog.executor!.id)).user)) }; const mentionable = ["", ""]; diff --git a/src/events/stickerCreate.ts b/src/events/stickerCreate.ts index b341ae9..0996302 100644 --- a/src/events/stickerCreate.ts +++ b/src/events/stickerCreate.ts @@ -4,7 +4,8 @@ import { AuditLogEvent, GuildAuditLogsEntry, Sticker } from "discord.js"; export const event = "stickerDelete"; export async function callback(client: NucleusClient, sticker: Sticker) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + if (!await isLogging(sticker.guild!.id, "stickerUpdate")) return; const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.EmojiCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; diff --git a/src/events/stickerDelete.ts b/src/events/stickerDelete.ts index ce26a85..fcfc3a6 100644 --- a/src/events/stickerDelete.ts +++ b/src/events/stickerDelete.ts @@ -4,7 +4,8 @@ import { AuditLogEvent, GuildAuditLogsEntry, Sticker } from "discord.js"; export const event = "stickerDelete"; export async function callback(client: NucleusClient, sticker: Sticker) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + if (!await isLogging(sticker.guild!.id, "stickerUpdate")) return; const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.StickerDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -14,8 +15,8 @@ export async function callback(client: NucleusClient, sticker: Sticker) { displayName: "Sticker Deleted", calculateType: "stickerUpdate", color: NucleusColors.red, - sticker: "GUILD.sticker.DELETE", - timestamp: auditLog.createdTimestamp + emoji: "GUILD.STICKER.DELETE", + timestamp: auditLog.createdTimestamp ?? Date.now() }, list: { stickerId: entry(sticker.id, `\`${sticker.id}\``), diff --git a/src/events/stickerUpdate.ts b/src/events/stickerUpdate.ts index ed01b71..aef28a4 100644 --- a/src/events/stickerUpdate.ts +++ b/src/events/stickerUpdate.ts @@ -4,8 +4,8 @@ import { AuditLogEvent, GuildAuditLogsEntry, Sticker } from "discord.js"; export const event = "stickerUpdate"; export async function callback(client: NucleusClient, oldSticker: Sticker, newSticker: Sticker) { - const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser } = client.logger; - + const { getAuditLog, isLogging, log, NucleusColors, entry, renderDelta, renderUser } = client.logger; + if (!await isLogging(newSticker.guild!.id, "stickerUpdate")) return; if (oldSticker.name === newSticker.name) return; const auditLog = (await getAuditLog(newSticker.guild!, AuditLogEvent.StickerUpdate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === newSticker.id)[0]!; diff --git a/src/events/threadCreate.ts b/src/events/threadCreate.ts index 6d3225c..f56e1bb 100644 --- a/src/events/threadCreate.ts +++ b/src/events/threadCreate.ts @@ -5,7 +5,8 @@ import type { NucleusClient } from "../utils/client.js"; export const event = "threadCreate"; export async function callback(client: NucleusClient, thread: ThreadChannel) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(thread.guild.id, "channelUpdate")) return; const auditLog = (await getAuditLog(thread.guild, AuditLogEvent.ThreadCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as ThreadChannel)!.id === thread.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -22,7 +23,7 @@ export async function callback(client: NucleusClient, thread: ThreadChannel) { calculateType: "channelUpdate", color: NucleusColors.green, emoji: "CHANNEL.TEXT.CREATE", - timestamp: thread.createdTimestamp + timestamp: thread.createdTimestamp ?? Date.now() }, list: { threadId: entry(thread.id, `\`${thread.id}\``), diff --git a/src/events/threadDelete.ts b/src/events/threadDelete.ts index 429f63a..bfac75e 100644 --- a/src/events/threadDelete.ts +++ b/src/events/threadDelete.ts @@ -5,7 +5,8 @@ import type { NucleusClient } from "../utils/client.js"; export const event = "threadDelete"; export async function callback(client: NucleusClient, thread: ThreadChannel) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(thread.guild.id, "channelUpdate")) return; const auditLog = (await getAuditLog(thread.guild, AuditLogEvent.ThreadDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as ThreadChannel)!.id === thread.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -22,7 +23,7 @@ export async function callback(client: NucleusClient, thread: ThreadChannel) { calculateType: "channelUpdate", color: NucleusColors.red, emoji: "CHANNEL.TEXT.DELETE", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { threadId: entry(thread.id, `\`${thread.id}\``), @@ -38,7 +39,7 @@ export async function callback(client: NucleusClient, thread: ThreadChannel) { membersInThread: entry(thread.memberCount, thread.memberCount!.toString()), deletedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), created: entry(thread.createdTimestamp, renderDelta(thread.createdTimestamp!)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())) + deleted: entry(Date.now(), renderDelta(Date.now())) }, hidden: { guild: thread.guild.id diff --git a/src/events/threadUpdate.ts b/src/events/threadUpdate.ts index 555b17f..af792bc 100644 --- a/src/events/threadUpdate.ts +++ b/src/events/threadUpdate.ts @@ -6,7 +6,8 @@ import type { NucleusClient } from "../utils/client.js"; export const event = "threadUpdate"; export async function callback(client: NucleusClient, oldThread: ThreadChannel, newThread: ThreadChannel) { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; + if (!await isLogging(newThread.guild.id, "channelUpdate")) return; const auditLog = (await getAuditLog(newThread.guild, AuditLogEvent.ThreadUpdate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as ThreadChannel)!.id === newThread.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; @@ -37,7 +38,7 @@ export async function callback(client: NucleusClient, oldThread: ThreadChannel, ); } if (!(Object.keys(list).length - 3)) return; - list["updated"] = entry(new Date().getTime(), renderDelta(new Date().getTime())); + list["updated"] = entry(Date.now(), renderDelta(Date.now())); list["updatedBy"] = entry(auditLog.executor!.id, renderUser(auditLog.executor!)); const data = { meta: { @@ -46,7 +47,7 @@ export async function callback(client: NucleusClient, oldThread: ThreadChannel, calculateType: "channelUpdate", color: NucleusColors.yellow, emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: list, hidden: { diff --git a/src/events/webhookUpdate.ts b/src/events/webhookUpdate.ts index e5f07dd..fb18f48 100644 --- a/src/events/webhookUpdate.ts +++ b/src/events/webhookUpdate.ts @@ -10,7 +10,8 @@ interface accType { export async function callback(client: NucleusClient, channel: Discord.GuildChannel) { try { - const { getAuditLog, log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; + const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; + if (!await isLogging(channel.guild.id, "webhookUpdate")) return; const auditCreate = (await getAuditLog(channel.guild, AuditLogEvent.WebhookCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Webhook)!.id === channel.id)[0]!; const auditDelete = (await getAuditLog(channel.guild, AuditLogEvent.WebhookDelete)) @@ -46,7 +47,7 @@ export async function callback(client: NucleusClient, channel: Discord.GuildChan (auditUpdate.target! as Extract).createdTimestamp, renderDelta((auditUpdate.target! as Extract).createdTimestamp) ); - list["edited"] = entry(after["editedTimestamp"]!, renderDelta(new Date().getTime())); + list["edited"] = entry(after["editedTimestamp"]!, renderDelta(Date.now())); list["editedBy"] = entry(auditUpdate.executor!.id, renderUser(auditUpdate.executor!)); action = "Update"; } else if (deleteTimestamp > createTimestamp && deleteTimestamp > updateTimestamp && auditDelete) { @@ -61,7 +62,7 @@ export async function callback(client: NucleusClient, channel: Discord.GuildChan name: entry(before["name"]!, `${before["name"]}`), channel: entry(before["channel_id"]!, renderChannel((await client.channels.fetch(before["channel_id"]!)) as GuildChannel)), created: entry((auditDelete.target! as Extract).createdTimestamp, renderDelta((auditDelete.target! as Extract).createdTimestamp)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), + deleted: entry(Date.now(), renderDelta(Date.now())), deletedBy: entry( auditDelete.executor!.id, renderUser((await channel.guild.members.fetch(auditDelete.executor!.id)).user) @@ -83,7 +84,7 @@ export async function callback(client: NucleusClient, channel: Discord.GuildChan auditCreate.executor!.id, renderUser((await channel.guild.members.fetch(auditCreate.executor!.id)).user) ), - created: entry(new Date().getTime(), renderDelta(new Date().getTime())) + created: entry(Date.now(), renderDelta(Date.now())) }; } const cols = { @@ -98,7 +99,7 @@ export async function callback(client: NucleusClient, channel: Discord.GuildChan calculateType: "webhookUpdate", color: NucleusColors[cols[action] as keyof typeof NucleusColors], emoji: "WEBHOOK." + action.toUpperCase(), - timestamp: new Date().getTime() + timestamp: Date.now() }, list: list, hidden: { diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 865089b..111f1a0 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -265,12 +265,12 @@ export default async function (interaction: CommandInteraction | MessageComponen calculateType: "ticketUpdate", color: NucleusColors.red, emoji: "GUILD.TICKET.CLOSE", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"), deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)), - deleted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())) + deleted: entry(Date.now().toString(), renderDelta(Date.now())) }, hidden: { guild: interaction.guild!.id diff --git a/src/utils/client.ts b/src/utils/client.ts index 5286814..32c9f7a 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,4 +1,4 @@ -import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits, Collection } from 'discord.js'; +import Discord, { Client, Interaction, AutocompleteInteraction, Collection } from 'discord.js'; import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; @@ -35,13 +35,7 @@ class NucleusClient extends Client { } | undefined,{name: string, description: string}]> = {}; fetchedCommands = new Collection(); constructor(database: typeof NucleusClient.prototype.database) { - super({ intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildPresences, - GatewayIntentBits.GuildMembers - ]}); + super({ intents: 0b1100011011011111111111}); this.database = database; } } diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index a4ede3f..693ad00 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -215,7 +215,7 @@ export default async function register() { } else { console.log(`${colors.blue}Registering commands in production mode${colors.none}`) const guild = await client.guilds.fetch(config.developmentGuildID); - await guild.commands.set([]); // TODO: Why isn't this removing guild commands? + await guild.commands.set([]); await client.application?.commands.set(commandList); } } diff --git a/src/utils/database.ts b/src/utils/database.ts index aa7fd36..d25cfdb 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -261,7 +261,7 @@ export class Premium { async checkAllPremium() { const entries = await this.premium.find({}).toArray(); for(const {user, expiresAt} of entries) { - if(expiresAt) expiresAt < new Date().getTime() ? await this.premium.deleteOne({user: user}) : null; + if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: user}) : null; const member = await (await client.guilds.fetch("684492926528651336")).members.fetch(user) let level: number = 0; if (member.roles.cache.has("1066468879309750313")) { @@ -277,7 +277,7 @@ export class Premium { if (level > 0) { await this.updateUser(user, level); } else { - await this.premium.updateOne({ user: user }, { expiresAt: (new Date().getTime() + (1000*60*60*24*3)) }) + await this.premium.updateOne({ user: user }, { expiresAt: (Date.now() + (1000*60*60*24*3)) }) } } } diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts index 3c9d6ca..60d525c 100644 --- a/src/utils/eventScheduler.ts +++ b/src/utils/eventScheduler.ts @@ -43,12 +43,12 @@ class EventScheduler { calculateType: "guildMemberPunish", color: NucleusColors.green, emoji: "PUNISH.MUTE.GREEN", - timestamp: new Date().getTime() + timestamp: Date.now() }, list: { memberId: entry(user.user.id, `\`${user.user.id}\``), name: entry(user.user.id, renderUser(user.user)), - unmuted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), + unmuted: entry(Date.now().toString(), renderDelta(Date.now())), unmutedBy: entry(null, "*Time out ended*") }, hidden: { diff --git a/src/utils/log.ts b/src/utils/log.ts index 64062ac..2d15192 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -7,6 +7,33 @@ import client from "./client.js"; const wait = promisify(setTimeout); +export interface LoggerOptions { + meta: { + type: string; + displayName: string; + calculateType: string; + color: number; + emoji: string; + timestamp: number; + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + list: any; + hidden: { + guild: string; + }, + separate?: { + start?: string; + end?: string; + } +} + +async function isLogging(guild: string, type: string): Promise { + const config = await client.database.guilds.read(guild); + if (!config.logging.logs.enabled) return false; + if (!config.logging.logs.channel) return false; + if (!toHexArray(config.logging.logs.toLog).includes(type)) { return false; } + return true; +} export const Logger = { renderUser(user: Discord.User | string) { @@ -50,13 +77,9 @@ export const Logger = { const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map(m => m) return auditLog as Discord.GuildAuditLogsEntry[]; }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async log(log: any): Promise { + async log(log: LoggerOptions): Promise { + if (!await isLogging(log.hidden.guild, log.meta.calculateType)) return; const config = await client.database.guilds.read(log.hidden.guild); - if (!config.logging.logs.enabled) return; - if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) { - return; - } if (config.logging.logs.channel) { const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null; const description: Record = {}; @@ -84,8 +107,8 @@ export const Logger = { channel.send({ embeds: [embed] }); } } - } + }, + isLogging }; - export default {}; diff --git a/src/utils/temp/generateFileName.ts b/src/utils/temp/generateFileName.ts index 3aab64c..109478d 100644 --- a/src/utils/temp/generateFileName.ts +++ b/src/utils/temp/generateFileName.ts @@ -12,7 +12,7 @@ export default function generateFileName(ending: string): string { if (fs.existsSync(`./${fileName}`)) { fileName = generateFileName(ending); } - client.database.eventScheduler.schedule("deleteFile", (new Date().getTime() + 60 * 1000).toString(), { + client.database.eventScheduler.schedule("deleteFile", (Date.now() + 60 * 1000).toString(), { fileName: `${fileName}.${ending}` }); return path.join(__dirname, fileName + "." + ending); From e35c4596ee114955cd820487604b0ed700da4b6f Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Mon, 20 Feb 2023 12:47:12 -0500 Subject: [PATCH 46/74] fixing event logs --- src/events/emojiDelete.ts | 2 +- src/events/emojiUpdate.ts | 2 +- src/events/guildMemberUpdate.ts | 57 ++++++++++++++++++++++++++++++--- src/events/stickerCreate.ts | 2 +- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/events/emojiDelete.ts b/src/events/emojiDelete.ts index f3c58fa..c4b488e 100644 --- a/src/events/emojiDelete.ts +++ b/src/events/emojiDelete.ts @@ -6,7 +6,7 @@ export const event = "emojiDelete"; export async function callback(client: NucleusClient, emoji: GuildEmoji) { const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; if (!await isLogging(emoji.guild.id, "emojiUpdate")) return; - const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiCreate)) + const auditLog = (await getAuditLog(emoji.guild, AuditLogEvent.EmojiDelete)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === emoji.id)[0]; if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; diff --git a/src/events/emojiUpdate.ts b/src/events/emojiUpdate.ts index 59b4488..98ff558 100644 --- a/src/events/emojiUpdate.ts +++ b/src/events/emojiUpdate.ts @@ -7,7 +7,7 @@ export async function callback(client: NucleusClient, oldEmoji: GuildEmoji, newE const { getAuditLog, log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderEmoji } = client.logger; if (!(await isLogging(newEmoji.guild.id, "emojiUpdate"))) return; - const auditLog = (await getAuditLog(newEmoji.guild, AuditLogEvent.EmojiCreate)) + const auditLog = (await getAuditLog(newEmoji.guild, AuditLogEvent.EmojiUpdate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildEmoji)!.id === newEmoji.id)[0]; if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 81b2337..412ac96 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -1,15 +1,60 @@ import { AuditLogEvent, GuildAuditLogsEntry, GuildMember } from "discord.js"; import type { NucleusClient } from "../utils/client.js"; +import type { LoggerOptions } from "../utils/log.js"; export const event = "guildMemberUpdate"; export async function callback(client: NucleusClient, before: GuildMember, after: GuildMember) { - const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; - if (!await isLogging(after.guild.id, "memberUpdate")) return; - const auditLog = (await getAuditLog(after.guild, AuditLogEvent.EmojiCreate)) + const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; + + const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberUpdate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0]; if (!auditLog) return; if (auditLog.executor!.id === client.user!.id) return; + if(!before.roles.cache.equals(after.roles.cache)) { + let rolesAdded = after.roles.cache.filter(role => !before.roles.cache.has(role.id)); + let rolesRemoved = before.roles.cache.filter(role => !after.roles.cache.has(role.id)); + let displayName = "Roles Removed"; + let color = NucleusColors.red; + let emoji = "GUILD.ROLES.DELETE"; + if(rolesAdded.size > 0 && rolesRemoved.size > 0) {displayName = "Roles Changed"; color = NucleusColors.yellow; emoji = "GUILD.ROLES.EDIT";} + else if(rolesAdded.size > 0) {displayName = "Roles Added"; color = NucleusColors.green; emoji = "GUILD.ROLES.CREATE";} + let removedEntry = rolesRemoved.map(role => role.id); + let addedEntry = rolesAdded.map(role => role.id); + + let list = { + memberId: entry(after.id, `\`${after.id}\``), + name: entry(after.user.id, renderUser(after.user)), + changed: entry(Date.now(), renderDelta(Date.now())), + changedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)) + }; + + let data: LoggerOptions = { + meta: { + type: "memberUpdate", + displayName: displayName, + calculateType: "guildMemberUpdate", + color: color, + emoji: emoji, + timestamp: Date.now() + }, + list: { + + }, + hidden: { + guild: after.guild.id + } + }; + + if(rolesAdded.size > 0) { + list = Object.assign(list, {rolesAdded: entry(addedEntry, addedEntry.map(id => `<@&${id}>`).join(", "))}); + } + if(rolesRemoved.size > 0) { + list = Object.assign(list, {rolesRemoved: entry(removedEntry, removedEntry.map(id => `<@&${id}>`).join(", "))}); + } + data = Object.assign(data, {list: list}); + log(data); + } if (before.nickname !== after.nickname) { await client.database.history.create( "nickname", @@ -42,7 +87,8 @@ export async function callback(client: NucleusClient, before: GuildMember, after } }; log(data); - } else if ( + } + if ( (before.communicationDisabledUntilTimestamp ?? 0) < Date.now() && (after.communicationDisabledUntil ?? 0) > Date.now() ) { @@ -86,7 +132,8 @@ export async function callback(client: NucleusClient, before: GuildMember, after user: after.id, expires: after.communicationDisabledUntilTimestamp }); - } else if ( + } + if ( after.communicationDisabledUntil === null && before.communicationDisabledUntilTimestamp !== null && Date.now() >= auditLog.createdTimestamp diff --git a/src/events/stickerCreate.ts b/src/events/stickerCreate.ts index 0996302..767e004 100644 --- a/src/events/stickerCreate.ts +++ b/src/events/stickerCreate.ts @@ -6,7 +6,7 @@ export const event = "stickerDelete"; export async function callback(client: NucleusClient, sticker: Sticker) { const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; if (!await isLogging(sticker.guild!.id, "stickerUpdate")) return; - const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.EmojiCreate)) + const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.StickerCreate)) .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; const data = { From 1807fb31d9dd9fcca6077397402e136e4ac9ace1 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Mon, 20 Feb 2023 14:33:48 -0500 Subject: [PATCH 47/74] fixing eslint --- src/commands/help.ts | 6 +- src/commands/nucleus/premium.ts | 108 +++++++++++------------ src/commands/server/buttons.ts | 14 +-- src/commands/settings/automod.ts | 18 ++-- src/commands/settings/autopublish.ts | 4 +- src/commands/settings/logs/attachment.ts | 4 +- src/commands/settings/logs/events.ts | 6 +- src/commands/settings/logs/warnings.ts | 2 +- src/commands/settings/tickets.ts | 7 +- src/commands/settings/tracks.ts | 2 +- src/commands/settings/welcome.ts | 2 +- src/commands/tags/edit.ts | 6 +- src/commands/user/role.ts | 26 +++--- src/events/guildMemberUpdate.ts | 98 +++++++++++--------- src/events/messageCreate.ts | 6 +- src/events/stickerDelete.ts | 2 +- src/premium/createTranscript.ts | 12 +-- src/utils/createTemporaryStorage.ts | 4 +- src/utils/log.ts | 2 +- 19 files changed, 174 insertions(+), 155 deletions(-) diff --git a/src/commands/help.ts b/src/commands/help.ts index b6115f9..90ef133 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -100,7 +100,7 @@ const callback = async (interaction: CommandInteraction): Promise => { nameLocalized?: string; descriptionLocalized?: string; })[] = []; - //o ptions + //options if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") { const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup const Op2 = Op.options!.find(option => option.name === currentPath[2])! @@ -114,7 +114,7 @@ const callback = async (interaction: CommandInteraction): Promise => { options = Op.options ?? [] } } else { - options = current.options.filter(option => option.type !== ApplicationCommandOptionType.SubcommandGroup && option.type !== ApplicationCommandOptionType.Subcommand) || []; + options = current.options.filter(option => (option.type !== ApplicationCommandOptionType.SubcommandGroup) && (option.type !== ApplicationCommandOptionType.Subcommand)); } for(const option of options) { optionString += `> ${option.name} (${ApplicationCommandOptionType[option.type]})- ${option.description}\n` @@ -141,7 +141,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name)) ) if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) { - const subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand) ?? []; + const subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options ?? []; subcommandRow.components[0]! .addOptions( new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"), diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index e0d8d8d..354c303 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -28,7 +28,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const dbMember = await client.database.premium.fetchUser(interaction.user.id) let premium = `You do not have premium! You can't activate premium on any servers.`; let count = 0; - const {level, appliesTo} = dbMember || {level: 0, appliesTo: []} + const {level, appliesTo} = dbMember ?? {level: 0, appliesTo: []} if (level === 99) { premium = `You have Infinite Premium! You have been gifted this by the developers as a thank you. You can give premium to any and all servers you are in.`; count = 200; @@ -48,71 +48,65 @@ const callback = async (interaction: CommandInteraction): Promise => { premiumGuild = `\n\n**This server has premium!**` } - let closed = false; - do { + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + premium + firstDescription + premiumGuild + ) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ], + components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Primary) + .setLabel("Activate Premium here") + .setCustomId("premiumActivate") + .setDisabled(count <= 0 && hasPremium) + ) + ] + }); + + const filter = (i: ButtonInteraction) => i.customId === "premiumActivate" && i.user.id === interaction.user.id; + let i; + try { + i = await interaction.channel!.awaitMessageComponent<2>({ filter, time: 60000 }); + } catch (e) { + return; + } + i.deferUpdate(); + const guild = i.guild!; + if (count - appliesTo.length <= 0) { interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Premium") .setDescription( - premium + firstDescription + premiumGuild + `You have already activated premium on the maximum amount of servers!` + firstDescription ) - .setEmoji("NUCLEUS.LOGO") + .setEmoji("NUCLEUS.PREMIUMACTIVATE") .setStatus("Danger") ], - components: [ - new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel("Activate Premium here") - .setCustomId("premiumActivate") - .setDisabled(count <= 0 && hasPremium) + components: [] + }); + } else { + client.database.premium.addPremium(interaction.user.id, guild.id); + interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + `You have activated premium on this server!` + firstDescription ) - ] + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ], + components: [] }); - - const filter = (i: any) => i.customId === "premiumActivate" && i.user.id === interaction.user.id; - let i; - try { - i = await interaction.channel!.awaitMessageComponent({ filter, time: 60000 }); - } catch (e) { - return; - } - i.deferUpdate(); - const guild = i.guild!; - if (count - appliesTo.length <= 0) { - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Premium") - .setDescription( - `You have already activated premium on the maximum amount of servers!` + firstDescription - ) - .setEmoji("NUCLEUS.PREMIUMACTIVATE") - .setStatus("Danger") - ], - components: [] - }); - closed = true; - } else { - client.database.premium.addPremium(interaction.user.id, guild.id); - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Premium") - .setDescription( - `You have activated premium on this server!` + firstDescription - ) - .setEmoji("NUCLEUS.LOGO") - .setStatus("Danger") - ], - components: [] - }); - closed = true; - } - - } while (!closed); + } }; export { command }; diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts index bc90983..61db825 100644 --- a/src/commands/server/buttons.ts +++ b/src/commands/server/buttons.ts @@ -44,7 +44,7 @@ export const callback = async (interaction: CommandInteraction): Promise = }); let closed = false; - let data: Data = { + const data: Data = { buttons: [], title: null, description: null, @@ -201,9 +201,9 @@ export const callback = async (interaction: CommandInteraction): Promise = } case "send": { await i.deferUpdate(); - let channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel; - let components = new ActionRowBuilder(); - for(let button of data.buttons) { + const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel; + const components = new ActionRowBuilder(); + for(const button of data.buttons) { components.addComponents( new ButtonBuilder() .setCustomId(button) @@ -211,9 +211,9 @@ export const callback = async (interaction: CommandInteraction): Promise = .setStyle(ButtonStyle.Primary) ); } - let messageData: MessageCreateOptions = {components: [components]} + const messageData: MessageCreateOptions = {components: [components]} if (data.title || data.description) { - let e = new EmojiEmbed() + const e = new EmojiEmbed() if(data.title) e.setTitle(data.title); if(data.description) e.setDescription(data.description); if(data.color) e.setColor(data.color); @@ -224,7 +224,7 @@ export const callback = async (interaction: CommandInteraction): Promise = } } } else if(i.isStringSelectMenu()) { - try {await i.deferUpdate();} catch (err) {} + try {await i.deferUpdate();} catch (err) {console.log(err)} switch(i.customId) { case "color": { data.color = colors[i.values[0]!]!; diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index f10cf67..87b1844 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -646,9 +646,9 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, return current } -const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: { - channels: string[], - allowed: { +const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current?: { + channels?: string[], + allowed?: { roles: string[], user: string[] } @@ -729,12 +729,12 @@ const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, c } else { switch (i.customId) { case "toAdd": { - let channelEmbed = new EmojiEmbed() + const channelEmbed = new EmojiEmbed() .setTitle("Clean Settings") .setDescription(`Editing <#${i.values[0]}>`) .setEmoji("GUILD.SETTINGS.GREEN") .setStatus("Success") - let channelButtons = new ActionRowBuilder() + const channelButtons = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId("back") @@ -788,7 +788,13 @@ const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, c } while(!closed); - return current; + return current as { + channels: string[], + allowed: { + roles: string[], + user: string[] + } + }; } diff --git a/src/commands/settings/autopublish.ts b/src/commands/settings/autopublish.ts index c4b9b56..f2c367f 100644 --- a/src/commands/settings/autopublish.ts +++ b/src/commands/settings/autopublish.ts @@ -58,7 +58,7 @@ export const callback = async (interaction: CommandInteraction): Promise = }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction; } catch (e) { closed = true; - break; + continue; } if(i.isButton()) { @@ -87,7 +87,7 @@ export const callback = async (interaction: CommandInteraction): Promise = export const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; - const me = interaction.guild!.members.me as Discord.GuildMember; + const me = interaction.guild!.members.me!; if (!member.permissions.has("ManageMessages")) return "You must have the *Manage Messages* permission to use this command"; if (_partial) return true; diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 545e3ff..a3b24ff 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -18,7 +18,7 @@ const callback = async (interaction: CommandInteraction): Promise => { fetchReply: true }) - if(!client.database.premium.hasPremium(interaction.guild!.id)) return interaction.editReply({ + if(!await client.database.premium.hasPremium(interaction.guild!.id)) return interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Premium Required") @@ -77,7 +77,7 @@ const callback = async (interaction: CommandInteraction): Promise => { })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { closed = true; - break; + continue; } await i.deferUpdate(); if(i.isButton()) { diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index a1f24fa..eeef8fb 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -104,7 +104,7 @@ const callback = async (interaction: CommandInteraction): Promise => { `**Channel:** ${data.channel ? `<#${data.channel}>` : "None"}\n` ) - let components: ActionRowBuilder[] = [channelMenu, buttons]; + const components: ActionRowBuilder[] = [channelMenu, buttons]; if(show) components.push(toLogMenu); await interaction.editReply({ @@ -120,7 +120,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }) as ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction; } catch (e) { closed = true; - break; + continue; } await i.deferUpdate(); @@ -147,7 +147,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } } else if(i.isStringSelectMenu()) { - let hex = toHexInteger(i.values); + const hex = toHexInteger(i.values); data.toLog = hex; } else if(i.isChannelSelectMenu()) { data.channel = i.values[0]!; diff --git a/src/commands/settings/logs/warnings.ts b/src/commands/settings/logs/warnings.ts index 6e5482c..25f9064 100644 --- a/src/commands/settings/logs/warnings.ts +++ b/src/commands/settings/logs/warnings.ts @@ -66,7 +66,7 @@ const callback = async (interaction: CommandInteraction): Promise => { })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { closed = true; - break; + continue; } await i.deferUpdate(); if(i.isButton()) { diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index af74475..2e046bf 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -154,7 +154,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setCustomId("maxTickets") .setPlaceholder("Enter a number") .setRequired(false) - .setValue(ticketData.maxTickets.toString() ?? "") + .setValue(ticketData.maxTickets.toString()) .setMinLength(1) .setMaxLength(3) .setStyle(TextInputStyle.Short) @@ -187,7 +187,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } if (!out || out.isButton()) continue; out = out as ModalSubmitInteraction; - let toAdd = out.fields.getTextInputValue("maxTickets"); + const toAdd = out.fields.getTextInputValue("maxTickets"); if(isNaN(parseInt(toAdd))) { errorMessage = "You entered an invalid number - No changes were made"; break; @@ -394,11 +394,10 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t toAdd = toAdd.substring(0, 80); try { if(!data.customTypes) data.customTypes = []; - data.customTypes?.push(toAdd); + data.customTypes.push(toAdd); } catch { continue; } - data.customTypes = data.customTypes ?? []; if (!data.customTypes.includes(toAdd)) { data.customTypes.push(toAdd); } diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 8d2d59d..d9d485d 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -217,7 +217,7 @@ const editTrack = async (interaction: ButtonInteraction | StringSelectMenuIntera ) .setStatus("Success") - let comps: ActionRowBuilder[] = [roleSelect, buttons]; + const comps: ActionRowBuilder[] = [roleSelect, buttons]; if(current.track.length >= 1) comps.splice(1, 0, selectMenu); interaction.editReply({embeds: [embed], components: comps}); diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index ae55fc0..7584624 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -215,7 +215,7 @@ const callback = async (interaction: CommandInteraction): Promise => { out = null; } if(!out) break; - data.message = out.fields.getTextInputValue("message") ?? null; + data.message = out.fields.getTextInputValue("message"); break; } case "save": { diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts index a80869e..7e297c8 100644 --- a/src/commands/tags/edit.ts +++ b/src/commands/tags/edit.ts @@ -19,9 +19,9 @@ const command = (builder: SlashCommandSubcommandBuilder) => const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; - const name = interaction.options.get("name")?.value as string; - const value = interaction.options.get("value")?.value as string ?? ""; - const newname = interaction.options.get("newname")?.value as string ?? ""; + const name = (interaction.options.get("name")?.value ?? "") as string; + const value = (interaction.options.get("value")?.value ?? "") as string; + const newname = (interaction.options.get("newname")?.value ?? "") as string; if (!newname && !value) return await interaction.reply({ embeds: [ diff --git a/src/commands/user/role.ts b/src/commands/user/role.ts index 5aa8782..41820ac 100644 --- a/src/commands/user/role.ts +++ b/src/commands/user/role.ts @@ -27,12 +27,12 @@ const callback = async (interaction: CommandInteraction): Promise => { let member = interaction.options.getMember("user") as GuildMember | null; if(!member) { - let memberEmbed = new EmojiEmbed() + const memberEmbed = new EmojiEmbed() .setTitle("Role") .setDescription(`Please choose a member to edit the roles of.`) .setEmoji("GUILD.ROLES.CREATE") .setStatus("Success"); - let memberChooser = new ActionRowBuilder().addComponents( + const memberChooser = new ActionRowBuilder().addComponents( new UserSelectMenuBuilder() .setCustomId("memberChooser") .setPlaceholder("Select a member") @@ -48,7 +48,6 @@ const callback = async (interaction: CommandInteraction): Promise => { return; } - if(!i) return; memberEmbed.setDescription(`Editing roles for ${renderUser(i.values[0]!)}`); await i.deferUpdate(); await interaction.editReply({ embeds: LoadingEmbed, components: [] }) @@ -67,14 +66,17 @@ const callback = async (interaction: CommandInteraction): Promise => { ); do { + + const removing = rolesToChange.filter((r) => member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0]) + const adding = rolesToChange.filter((r) => !member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0]) const embed = new EmojiEmbed() .setTitle("Role") .setDescription( `${getEmojiByName("ICONS.EDIT")} Editing roles for <@${member.id}>\n\n` + `Adding:\n` + - `${listToAndMore(rolesToChange.filter((r) => !member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0]) || ["None"], 5)}\n` + + `${listToAndMore(adding.length > 0 ? adding : ["None"], 5)}\n` + `Removing:\n` + - `${listToAndMore(rolesToChange.filter((r) => member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0]) || ["None"], 5)}\n` + `${listToAndMore(removing.length > 0 ? removing : ["None"], 5)}\n` ) .setEmoji("GUILD.ROLES.CREATE") .setStatus("Success"); @@ -99,18 +101,18 @@ const callback = async (interaction: CommandInteraction): Promise => { try { i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 }) as RoleSelectMenuInteraction | ButtonInteraction; } catch (e) { - return; + closed = true; + continue; } - if(!i) return; i.deferUpdate(); if(i.isButton()) { switch(i.customId) { - case "roleSave": + case "roleSave": { const roles = rolesToChange.map((r) => interaction.guild?.roles.cache.get(r)!); await interaction.editReply({ embeds: LoadingEmbed, components: [] }); - let rolesToAdd: Role[] = []; - let rolesToRemove: Role[] = []; + const rolesToAdd: Role[] = []; + const rolesToRemove: Role[] = []; for(const role of roles) { if(!canEdit(role, interaction.member as GuildMember, interaction.guild?.members.me!)[1]) continue; if(member.roles.cache.has(role.id)) { @@ -123,10 +125,12 @@ const callback = async (interaction: CommandInteraction): Promise => { await member.roles.remove(rolesToRemove); rolesToChange = []; break; - case "roleDiscard": + } + case "roleDiscard": { rolesToChange = []; await interaction.editReply({ embeds: LoadingEmbed, components: [] }); break; + } } } else { rolesToChange = i.values; diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 412ac96..b674060 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -1,60 +1,76 @@ import { AuditLogEvent, GuildAuditLogsEntry, GuildMember } from "discord.js"; import type { NucleusClient } from "../utils/client.js"; import type { LoggerOptions } from "../utils/log.js"; +import { generalException } from "../utils/createTemporaryStorage.js"; export const event = "guildMemberUpdate"; export async function callback(client: NucleusClient, before: GuildMember, after: GuildMember) { const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; - const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberUpdate)) - .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0]; - if (!auditLog) return; - if (auditLog.executor!.id === client.user!.id) return; if(!before.roles.cache.equals(after.roles.cache)) { - let rolesAdded = after.roles.cache.filter(role => !before.roles.cache.has(role.id)); - let rolesRemoved = before.roles.cache.filter(role => !after.roles.cache.has(role.id)); - let displayName = "Roles Removed"; - let color = NucleusColors.red; - let emoji = "GUILD.ROLES.DELETE"; - if(rolesAdded.size > 0 && rolesRemoved.size > 0) {displayName = "Roles Changed"; color = NucleusColors.yellow; emoji = "GUILD.ROLES.EDIT";} - else if(rolesAdded.size > 0) {displayName = "Roles Added"; color = NucleusColors.green; emoji = "GUILD.ROLES.CREATE";} - let removedEntry = rolesRemoved.map(role => role.id); - let addedEntry = rolesAdded.map(role => role.id); + const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberRoleUpdate)) + .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0]; + if (!auditLog) return; + if (client.noLog.includes(`${after.guild.id}${after.id}${auditLog.id}`)) return; + generalException(`${after.guild.id}${after.id}${auditLog.id}`); + if (auditLog.executor!.id !== client.user!.id) { + const rolesAdded = after.roles.cache.filter(role => !before.roles.cache.has(role.id)); + const rolesRemoved = before.roles.cache.filter(role => !after.roles.cache.has(role.id)); + let displayName = "Roles Removed"; + let color = NucleusColors.red; + let emoji = "GUILD.ROLES.DELETE"; + if(rolesAdded.size > 0 && rolesRemoved.size > 0) {displayName = "Roles Changed"; color = NucleusColors.yellow; emoji = "GUILD.ROLES.EDIT";} + else if(rolesAdded.size > 0) {displayName = "Roles Added"; color = NucleusColors.green; emoji = "GUILD.ROLES.CREATE";} + const removedEntry = rolesRemoved.map(role => role.id); + const addedEntry = rolesAdded.map(role => role.id); - let list = { - memberId: entry(after.id, `\`${after.id}\``), - name: entry(after.user.id, renderUser(after.user)), - changed: entry(Date.now(), renderDelta(Date.now())), - changedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)) - }; - - let data: LoggerOptions = { - meta: { - type: "memberUpdate", - displayName: displayName, - calculateType: "guildMemberUpdate", - color: color, - emoji: emoji, - timestamp: Date.now() - }, - list: { + let list = { + memberId: entry(after.id, `\`${after.id}\``), + name: entry(after.user.id, renderUser(after.user)), + }; - }, - hidden: { - guild: after.guild.id + if (rolesAdded.size > 0) { + list = Object.assign(list, {rolesAdded: entry(addedEntry, addedEntry.map(id => `<@&${id}>`).join(", "))}); + } + if (rolesRemoved.size > 0) { + list = Object.assign(list, {rolesRemoved: entry(removedEntry, removedEntry.map(id => `<@&${id}>`).join(", "))}); } - }; - if(rolesAdded.size > 0) { - list = Object.assign(list, {rolesAdded: entry(addedEntry, addedEntry.map(id => `<@&${id}>`).join(", "))}); - } - if(rolesRemoved.size > 0) { - list = Object.assign(list, {rolesRemoved: entry(removedEntry, removedEntry.map(id => `<@&${id}>`).join(", "))}); + list = Object.assign(list, { + changed: entry(Date.now(), renderDelta(Date.now())), + changedBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)) + }); + + let data: LoggerOptions = { + meta: { + type: "memberUpdate", + displayName: displayName, + calculateType: "guildMemberUpdate", + color: color, + emoji: emoji, + timestamp: Date.now() + }, + list: {}, + hidden: { + guild: after.guild.id + } + }; + + if(rolesAdded.size > 0) { + list = Object.assign(list, {rolesAdded: entry(addedEntry, addedEntry.map(id => `<@&${id}>`).join(", "))}); + } + if(rolesRemoved.size > 0) { + list = Object.assign(list, {rolesRemoved: entry(removedEntry, removedEntry.map(id => `<@&${id}>`).join(", "))}); + } + data = Object.assign(data, {list: list}); + log(data); } - data = Object.assign(data, {list: list}); - log(data); } + const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberUpdate)) + .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildMember)!.id === after.id)[0]; + if (!auditLog) return; + if (auditLog.executor!.id === client.user!.id) return; if (before.nickname !== after.nickname) { await client.database.history.create( "nickname", diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 7f8e5d1..44017d7 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -26,9 +26,9 @@ export async function callback(_client: NucleusClient, message: Message) { const content = message.content.toLowerCase() || ""; const config = await client.memory.readGuildInfo(message.guild.id); if(config.filters.clean.channels.includes(message.channel.id)) { - let memberRoles = message.member!.roles.cache.map(role => role.id); - let roleAllow = config.filters.clean.allowed.roles.some(role => memberRoles.includes(role)); - let userAllow = config.filters.clean.allowed.user.includes(message.author.id); + const memberRoles = message.member!.roles.cache.map(role => role.id); + const roleAllow = config.filters.clean.allowed.roles.some(role => memberRoles.includes(role)); + const userAllow = config.filters.clean.allowed.user.includes(message.author.id); if(!roleAllow && !userAllow) return await message.delete(); } diff --git a/src/events/stickerDelete.ts b/src/events/stickerDelete.ts index fcfc3a6..87a991f 100644 --- a/src/events/stickerDelete.ts +++ b/src/events/stickerDelete.ts @@ -16,7 +16,7 @@ export async function callback(client: NucleusClient, sticker: Sticker) { calculateType: "stickerUpdate", color: NucleusColors.red, emoji: "GUILD.STICKER.DELETE", - timestamp: auditLog.createdTimestamp ?? Date.now() + timestamp: auditLog.createdTimestamp }, list: { stickerId: entry(sticker.id, `\`${sticker.id}\``), diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 111f1a0..fb6dde7 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -112,9 +112,9 @@ export default async function (interaction: CommandInteraction | MessageComponen } }); - let interactionMember = await interaction.guild?.members.fetch(interaction.user.id) + const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) - let newOut: Transcript = { + const newOut: Transcript = { type: "ticket", guild: interaction.guild!.id, channel: interaction.channel!.id, @@ -131,7 +131,7 @@ export default async function (interaction: CommandInteraction | MessageComponen } if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; messages.reverse().forEach((message) => { - let msg: TranscriptMessage = { + const msg: TranscriptMessage = { id: message.id, author: { username: message.author.username, @@ -146,7 +146,7 @@ export default async function (interaction: CommandInteraction | MessageComponen if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; if (message.content) msg.content = message.content; if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { - let obj: TranscriptEmbed = {}; + const obj: TranscriptEmbed = {}; if (embed.title) obj.title = embed.title; if (embed.description) obj.description = embed.description; if (embed.fields.length > 0) obj.fields = embed.fields.map(field => { @@ -163,7 +163,7 @@ export default async function (interaction: CommandInteraction | MessageComponen return obj; }); if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { - let obj: TranscriptComponent = { + const obj: TranscriptComponent = { type: child.type } if (child.type === ComponentType.Button) { @@ -175,7 +175,7 @@ export default async function (interaction: CommandInteraction | MessageComponen return obj })); if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp; - if (message.flags) msg.flags = message.flags.toArray(); + msg.flags = message.flags.toArray(); if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; diff --git a/src/utils/createTemporaryStorage.ts b/src/utils/createTemporaryStorage.ts index a684d9d..e8a8073 100644 --- a/src/utils/createTemporaryStorage.ts +++ b/src/utils/createTemporaryStorage.ts @@ -1,6 +1,6 @@ import client from "./client.js"; -function generalException(location: string) { +export function generalException(location: string) { client.noLog.push(location); setTimeout(() => { client.noLog = client.noLog.filter((i: string) => { @@ -29,4 +29,4 @@ export function preloadPage(target: string, command: string, message: string) { }) client.preloadPage = Object.fromEntries(object); }, 60 * 5 * 1000); -} \ No newline at end of file +} diff --git a/src/utils/log.ts b/src/utils/log.ts index 2d15192..87549dd 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -94,7 +94,7 @@ export const Logger = { } }); if (channel) { - log.separate = log.separate || {}; + log.separate = log.separate ?? {}; const embed = new Discord.EmbedBuilder() .setTitle(`${getEmojiByName(log.meta.emoji)} ${log.meta.displayName}`) .setDescription( From ff37b598d4e8826698ad38583810ee762913e4ef Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Mon, 20 Feb 2023 15:09:02 -0500 Subject: [PATCH 48/74] fixed event logs other than webhookUpdate which is broken for an unknown reason --- src/events/stickerCreate.ts | 7 +++++-- src/events/stickerDelete.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/events/stickerCreate.ts b/src/events/stickerCreate.ts index 767e004..5d2e443 100644 --- a/src/events/stickerCreate.ts +++ b/src/events/stickerCreate.ts @@ -1,14 +1,17 @@ import type { NucleusClient } from "../utils/client.js"; import { AuditLogEvent, GuildAuditLogsEntry, Sticker } from "discord.js"; +import { generalException } from "../utils/createTemporaryStorage.js"; -export const event = "stickerDelete"; +export const event = "stickerCreate"; export async function callback(client: NucleusClient, sticker: Sticker) { const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger; if (!await isLogging(sticker.guild!.id, "stickerUpdate")) return; const auditLog = (await getAuditLog(sticker.guild!, AuditLogEvent.StickerCreate)) - .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!; + .filter((entry: GuildAuditLogsEntry) => (entry.target as Sticker)!.id === sticker.id)[0]!; if (auditLog.executor!.id === client.user!.id) return; + if (client.noLog.includes(`${sticker.guild!.id}${auditLog.id}`)) return; + generalException(`${sticker.guild!.id}${auditLog.id}`); const data = { meta: { type: "stickerCreate", diff --git a/src/events/stickerDelete.ts b/src/events/stickerDelete.ts index 87a991f..d123f44 100644 --- a/src/events/stickerDelete.ts +++ b/src/events/stickerDelete.ts @@ -15,7 +15,7 @@ export async function callback(client: NucleusClient, sticker: Sticker) { displayName: "Sticker Deleted", calculateType: "stickerUpdate", color: NucleusColors.red, - emoji: "GUILD.STICKER.DELETE", + emoji: "GUILD.EMOJI.DELETE", timestamp: auditLog.createdTimestamp }, list: { From 96228bdb24b74f36d69a9ef079a1e2ce0f84bd64 Mon Sep 17 00:00:00 2001 From: pineafan Date: Tue, 21 Feb 2023 14:22:55 +0000 Subject: [PATCH 49/74] Fixed all typescript errors --- package.json | 3 +++ src/commands/server/buttons.ts | 2 +- src/commands/settings/autopublish.ts | 2 +- src/commands/settings/logs/attachment.ts | 2 +- src/commands/settings/logs/warnings.ts | 2 +- src/context/messages/purgeto.ts | 5 +++-- src/events/guildMemberUpdate.ts | 2 +- src/utils/dualCollector.ts | 13 ++++++------- src/utils/singleNotify.ts | 21 +++++++++++---------- tsconfig.json | 3 ++- 10 files changed, 30 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 888ceaf..96c6e7a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,9 @@ "typescript": "^4.9.4", "uuid": "^8.3.2" }, + "resolutions": { + "discord-api-types": "0.37.20" + }, "name": "nucleus", "version": "0.0.1", "description": "Nucleus: The core of your server", diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts index 61db825..e4bda0d 100644 --- a/src/commands/server/buttons.ts +++ b/src/commands/server/buttons.ts @@ -134,7 +134,7 @@ export const callback = async (interaction: CommandInteraction): Promise = let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; try { i = await interaction.channel!.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id, + filter: (i: Discord.Interaction) => i.user.id === interaction.user.id, time: 300000 }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; } catch (e) { diff --git a/src/commands/settings/autopublish.ts b/src/commands/settings/autopublish.ts index f2c367f..1dc97e0 100644 --- a/src/commands/settings/autopublish.ts +++ b/src/commands/settings/autopublish.ts @@ -53,7 +53,7 @@ export const callback = async (interaction: CommandInteraction): Promise = let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction; try { i = await interaction.channel!.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id, + filter: (i: Discord.Interaction) => i.user.id === interaction.user.id, time: 300000 }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction; } catch (e) { diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index a3b24ff..8331043 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -72,7 +72,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction; try { i = (await interaction.channel!.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id, + filter: (i: Discord.Interaction) => i.user.id === interaction.user.id, time: 300000 })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { diff --git a/src/commands/settings/logs/warnings.ts b/src/commands/settings/logs/warnings.ts index 25f9064..24249a2 100644 --- a/src/commands/settings/logs/warnings.ts +++ b/src/commands/settings/logs/warnings.ts @@ -61,7 +61,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction; try { i = (await interaction.channel!.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id, + filter: (i: Discord.Interaction) => i.user.id === interaction.user.id, time: 300000 })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts index b0211ec..4627bf2 100644 --- a/src/context/messages/purgeto.ts +++ b/src/context/messages/purgeto.ts @@ -1,7 +1,7 @@ import confirmationMessage from '../../utils/confirmationMessage.js'; import EmojiEmbed from '../../utils/generateEmojiEmbed.js'; import { LoadingEmbed } from '../../utils/defaults.js'; -import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildTextBasedChannel, MessageContextMenuCommandInteraction } from "discord.js"; +import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildTextBasedChannel, Message, MessageContextMenuCommandInteraction } from "discord.js"; import client from "../../utils/client.js"; import getEmojiByName from '../../utils/getEmojiByName.js'; import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from "../../utils/logTranscripts.js"; @@ -184,7 +184,8 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { } }; log(data); - const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(deleted.map((m) => m as Discord.Message))!); + const messages: Message[] = deleted.map(m => m).filter(m => m instanceof Message).map(m => m as Message); + const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(messages)!); const attachmentObject = { attachment: Buffer.from(transcript), name: `purge-${channel.id}-${Date.now()}.txt`, diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index b674060..5788f61 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -106,7 +106,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after } if ( (before.communicationDisabledUntilTimestamp ?? 0) < Date.now() && - (after.communicationDisabledUntil ?? 0) > Date.now() + new Date(after.communicationDisabledUntil ?? 0).getTime() > Date.now() ) { await client.database.history.create( "mute", diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index 072f73f..0b05779 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -10,15 +10,14 @@ export default async function ( try { out = await new Promise((resolve, _reject) => { const mes = m - .createMessageComponentCollector({ - filter: (m) => interactionFilter(m), - time: 300000 - }) - .on("collect", (m) => { + .createMessageComponentCollector({ + filter: (m) => interactionFilter(m), + time: 300000 + }) + .on("collect", (m) => { resolve(m); }); - const int = m.channel - .createMessageCollector({ + const int = m.channel.createMessageCollector({ filter: (m) => messageFilter(m), time: 300000 }) diff --git a/src/utils/singleNotify.ts b/src/utils/singleNotify.ts index 8e3aa60..6bf63e1 100644 --- a/src/utils/singleNotify.ts +++ b/src/utils/singleNotify.ts @@ -1,6 +1,7 @@ import client from "./client.js"; import EmojiEmbed from "./generateEmojiEmbed.js"; import { Record as ImmutableRecord } from "immutable"; +import type { TextChannel, ThreadChannel, NewsChannel } from "discord.js"; const severitiesType = ImmutableRecord({ Critical: "Danger", @@ -31,20 +32,20 @@ export default async function ( const channel = await client.channels.fetch(data.logging.staff.channel); if (!channel) return; if (!channel.isTextBased()) return; + const textChannel = channel as TextChannel | ThreadChannel | NewsChannel; + let messageData = {embeds: [ + new EmojiEmbed() + .setTitle(`${severity} notification`) + .setDescription(message) + .setStatus(severities.get(severity)) + .setEmoji("CONTROL.BLOCKCROSS") + ]} if (pings) { - await channel.send({ + messageData = Object.assign(messageData, { content: pings.map((ping) => `<@${ping}>`).join(" ") }); } - await channel.send({ - embeds: [ - new EmojiEmbed() - .setTitle(`${severity} notification`) - .setDescription(message) - .setStatus(severities.get(severity)) - .setEmoji("CONTROL.BLOCKCROSS") - ] - }); + await textChannel.send(messageData); } catch (err) { console.error(err); } diff --git a/tsconfig.json b/tsconfig.json index 7e6abdc..a67284d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "resolveJsonModule": true, "moduleResolution": "NodeNext", "skipLibCheck": true, - "noImplicitReturns": false + "noImplicitReturns": false, + "ignoreDeprecations": "5.0" }, "include": ["src/**/*", "src/index.d.ts"], "exclude": ["src/Unfinished/**/*"] From a2e39c7ffbb91dc0424c484f86fb508b974239f1 Mon Sep 17 00:00:00 2001 From: pineafan Date: Tue, 21 Feb 2023 18:37:32 +0000 Subject: [PATCH 50/74] Fixed all typescript errors (hopefully, github) --- .eslintrc.json | 2 +- .gitignore | 6 ++- .prettierignore | 2 +- SELF_HOSTING.md | 2 +- src/config/format.ts | 39 ++++++++++--------- src/config/main.d.ts | 21 ++++++++++ src/index.ts | 2 +- src/utils/client.ts | 2 +- src/utils/commandRegistration/register.ts | 2 +- .../slashCommandBuilder.ts | 2 +- src/utils/database.ts | 2 +- src/utils/eventScheduler.ts | 2 +- src/utils/performanceTesting/record.ts | 2 +- tsconfig.json | 2 +- 14 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 src/config/main.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 4b4e25d..165e759 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "es2020": true, "node": true }, - "ignorePatterns": ["dist/", "src/Unfinished/"], + "ignorePatterns": ["dist/", "src/Unfinished/", "src/config/main.d.ts", "*.js"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/strict", "prettier"], "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/.gitignore b/.gitignore index d731f0c..b8b8506 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,12 @@ dist/ .history/ node_modules/ src/config/* + +!src/config/*.d.ts !src/config/format.ts !src/config/default.json !src/config/emojis.json -src/config/main.json +src/config/main.ts .vscode/ .vim/ yarn-error.log @@ -16,4 +18,4 @@ src/utils/temp/*.jpeg src/utils/temp/*.jpg ClicksMigratingProblems/oldData/ -ClicksMigratingProblems/oldData copy/ \ No newline at end of file +ClicksMigratingProblems/oldData copy/ diff --git a/.prettierignore b/.prettierignore index bfedc85..7575e2d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,7 +5,7 @@ src/config/* !src/config/format.ts !src/config/default.json !src/config/emojis.json -src/config/main.json +!src/config/main.ts .vscode/ yarn-error.log yarn.lock diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md index 4f01364..1298cd4 100644 --- a/SELF_HOSTING.md +++ b/SELF_HOSTING.md @@ -23,7 +23,7 @@ However, you **must**: ## How to: -We hide the config file with our important data like the bot token. Below you can find a copy of `src/config/main.json`. +We hide the config file with our important data like the bot token. Below you can find a copy of `src/config/main.ts`. Alternatively, you can run `Installer.js` to generate it for you. ```json diff --git a/src/config/format.ts b/src/config/format.ts index b00b3e4..a80094e 100644 --- a/src/config/format.ts +++ b/src/config/format.ts @@ -1,3 +1,4 @@ + import fs from "fs"; import * as readLine from "node:readline/promises"; @@ -50,25 +51,27 @@ export default async function (walkthrough = false) { // } } - let json; + let json: typeof defaultDict; let out = true; try { - json = JSON.parse(fs.readFileSync("./src/config/main.json", "utf8")); + json = await import("./main.js") as unknown as typeof defaultDict; } catch (e) { - console.log("\x1b[31m⚠ No main.json found, creating one."); - console.log(" \x1b[2mYou can edit src/config/main.json directly using template written to the file.\x1b[0m\n"); + console.log("\x1b[31m⚠ No main.ts found, creating one."); + console.log(" \x1b[2mYou can edit src/config/main.ts directly using template written to the file.\x1b[0m\n"); out = false; - json = {}; + json = {} as typeof defaultDict; } - if (json) { - if (json.token === defaultDict["token"] || json.developmentToken === defaultDict["developmentToken"]) { - console.log("\x1b[31m⚠ No main.json found, creating one."); - console.log(" \x1b[2mYou can edit src/config/main.json directly using template written to the file.\x1b[0m\n"); + + if (Object.keys(json).length) { + if (json["token"] === defaultDict["token"] || json["developmentToken"] === defaultDict["developmentToken"]) { + console.log("\x1b[31m⚠ No main.ts found, creating one."); + console.log(" \x1b[2mYou can edit src/config/main.ts directly using template written to the file.\x1b[0m\n"); json = {}; } } + for (const key in defaultDict) { - if (!json[key]) { + if (Object.keys(json).includes(key)) { if (walkthrough) { switch (key) { case "enableDevelopment": { @@ -99,13 +102,13 @@ export default async function (walkthrough = false) { } } } else { - json[key] = defaultDict[key]; + json[key] = defaultDict[key]!; } } } - if (walkthrough && !json.mongoUrl) json.mongoUrl = "mongodb://127.0.0.1:27017"; - if (!json.mongoUrl.endsWith("/")) json.mongoUrl += "/"; - if (!json.baseUrl.endsWith("/")) json.baseUrl += "/"; + if (walkthrough && !(json["mongoUrl"] ?? false)) json["mongoUrl"] = "mongodb://127.0.0.1:27017"; + if (!((json["mongoUrl"] as string | undefined) ?? "").endsWith("/")) json["mongoUrl"] += "/"; + if (!((json["baseUrl"] as string | undefined) ?? "").endsWith("/")) json["baseUrl"] += "/"; let hosts; try { hosts = fs.readFileSync("/etc/hosts", "utf8").toString().split("\n"); @@ -114,16 +117,16 @@ export default async function (walkthrough = false) { "\x1b[31m⚠ No /etc/hosts found. Please ensure the file exists and is readable. (Windows is not supported, Mac and Linux users should not experience this error)" ); } - let localhost = hosts.find((line) => line.split(" ")[1] === "localhost"); + let localhost: string | undefined = hosts.find((line) => line.split(" ")[1] === "localhost"); if (localhost) { localhost = localhost.split(" ")[0]; } else { localhost = "127.0.0.1"; } - json.mongoUrl = json.mongoUrl.replace("localhost", localhost); - json.baseUrl = json.baseUrl.replace("localhost", localhost); + json["mongoUrl"] = (json["mongoUrl"]! as string).replace("localhost", localhost!); + json["baseUrl"] = (json["baseUrl"]! as string).replace("localhost", localhost!); - fs.writeFileSync("./src/config/main.json", JSON.stringify(json, null, 4)); + fs.writeFileSync("./src/config/main.ts", "export default " + JSON.stringify(json, null, 4) + ";"); if (walkthrough) { console.log("\x1b[32m✓ All properties added.\x1b[0m"); diff --git a/src/config/main.d.ts b/src/config/main.d.ts new file mode 100644 index 0000000..be6a253 --- /dev/null +++ b/src/config/main.d.ts @@ -0,0 +1,21 @@ +declare const config: { + developmentToken: string, + developmentGuildID: string, + enableDevelopment: boolean, + token: string, + managementGuildID: string, + owners: string[], + commandsFolder: string, + eventsFolder: string, + messageContextFolder: string, + userContextFolder: string, + verifySecret: string, + mongoUrl: string, + baseUrl: string, + pastebinApiKey: string, + pastebinUsername: string, + pastebinPassword: string, + rapidApiKey: string +}; + +export default config; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 306ca50..3c132bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import runServer from "./api/index.js"; import client from "./utils/client.js"; -import config from "./config/main.json" assert { type: "json" }; +import config from "./config/main.js"; import register from "./utils/commandRegistration/register.js"; import { record as recordPerformance } from "./utils/performanceTesting/record.js"; diff --git a/src/utils/client.ts b/src/utils/client.ts index 32c9f7a..b05ef5f 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -5,7 +5,7 @@ import type { VerifySchema } from "../reflex/verify.js"; import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache } from "../utils/database.js"; import EventScheduler from "../utils/eventScheduler.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js"; -import config from "../config/main.json" assert { type: "json" }; +import config from "../config/main.js"; class NucleusClient extends Client { diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 693ad00..1a19e2c 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -1,6 +1,6 @@ import type { CommandInteraction } from 'discord.js'; import Discord, { Interaction, SlashCommandBuilder, ApplicationCommandType } from 'discord.js'; -import config from "../../config/main.json" assert { type: "json" }; +import config from "../../config/main.js"; import client from "../client.js"; import fs from "fs"; import EmojiEmbed from '../generateEmojiEmbed.js'; diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index 9a72605..66291b3 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -1,6 +1,6 @@ import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from "discord.js"; import type { SlashCommandBuilder } from "discord.js"; -import config from "../../config/main.json" assert { type: "json" }; +import config from "../../config/main.js"; import getSubcommandsInFolder from "./getFilesInFolder.js"; import client from "../client.js"; import Discord from "discord.js"; diff --git a/src/utils/database.ts b/src/utils/database.ts index d25cfdb..239da13 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,6 +1,6 @@ import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; -import config from "../config/main.json" assert { type: "json" }; +import config from "../config/main.js"; import client from "../utils/client.js"; const mongoClient = new MongoClient(config.mongoUrl); diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts index 60d525c..bdd0d21 100644 --- a/src/utils/eventScheduler.ts +++ b/src/utils/eventScheduler.ts @@ -2,7 +2,7 @@ import { Agenda } from "@hokify/agenda"; import client from "./client.js"; import * as fs from "fs"; import * as path from "path"; -import config from "../config/main.json" assert { type: "json" }; +import config from "../config/main.js"; class EventScheduler { private agenda: Agenda; diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts index 17cfb1e..71883c5 100644 --- a/src/utils/performanceTesting/record.ts +++ b/src/utils/performanceTesting/record.ts @@ -2,7 +2,7 @@ import client from "../client.js"; import * as CP from 'child_process'; import * as process from 'process'; import systeminformation from "systeminformation"; -import config from "../../config/main.json" assert { type: "json" }; +import config from "../../config/main.js"; import singleNotify from "../singleNotify.js"; diff --git a/tsconfig.json b/tsconfig.json index a67284d..6339096 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,6 @@ "noImplicitReturns": false, "ignoreDeprecations": "5.0" }, - "include": ["src/**/*", "src/index.d.ts"], + "include": ["src/**/*", "src/*", "src/config/main.d.ts", "src/config/main.ts"], "exclude": ["src/Unfinished/**/*"] } From ed9794c9c0e45a5bacc745eed2972a502457434b Mon Sep 17 00:00:00 2001 From: pineafan Date: Tue, 21 Feb 2023 18:39:48 +0000 Subject: [PATCH 51/74] we dont use deno --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 96c6e7a..82924be 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "ansi-styles": "^6.1.0", "body-parser": "^1.20.0", "chalk": "^5.0.0", - "deno": "^0.1.1", "discord.js": "^14.7.1", "eslint": "^8.21.0", "express": "^4.18.1", From ca6b705784d6c1e4eca369f130717480298291b1 Mon Sep 17 00:00:00 2001 From: pineafan Date: Wed, 22 Feb 2023 18:14:47 +0000 Subject: [PATCH 52/74] No ts errors? --- package.json | 2 +- tsconfig.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 82924be..624e4b3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "uuid": "^8.3.2" }, "resolutions": { - "discord-api-types": "0.37.20" + "discord-api-types": "0.37.23" }, "name": "nucleus", "version": "0.0.1", diff --git a/tsconfig.json b/tsconfig.json index 6339096..537e3dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,6 @@ "moduleResolution": "NodeNext", "skipLibCheck": true, "noImplicitReturns": false, - "ignoreDeprecations": "5.0" }, "include": ["src/**/*", "src/*", "src/config/main.d.ts", "src/config/main.ts"], "exclude": ["src/Unfinished/**/*"] From a6ace0795dcee3e81d0b24907e25f2aa77e709b6 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 22 Feb 2023 13:15:16 -0500 Subject: [PATCH 53/74] no --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82924be..624e4b3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "uuid": "^8.3.2" }, "resolutions": { - "discord-api-types": "0.37.20" + "discord-api-types": "0.37.23" }, "name": "nucleus", "version": "0.0.1", From 686829f2b6eec1900717ea9e60c36c288c9258a6 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 22 Feb 2023 15:08:01 -0500 Subject: [PATCH 54/74] Webhooks work --- dump.zip | Bin 19058 -> 0 bytes src/events/webhookUpdate.ts | 19 +++++++++---------- src/utils/log.ts | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 dump.zip diff --git a/dump.zip b/dump.zip deleted file mode 100644 index c259f0e7e2d47f0739af5d8cfbd4ec1bc18247c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19058 zcmafaWl$!~vL+1f?hb>yyE_arxVyW&z~JsO4DRmk?(XjJ;_mLUbI#ox8|T}-u^o}! z{i8CXp32InGPAQ4Wx>GFLH>3139G36`{jS%5dJ<*Tx=Z}SuP3-{6#ula~E;go43`WlOb_wa8*H_}2!@l@M(8?5SV9Zhp zBD2AreC=;Gvr$+b;37B{;g%NyZ**CH{Wx5Xc&KE(uy{YwTT|9d*fDlZ4gr5GJAz`a zLn#b~N$}00BHGEhkbiVOO(1=KUcQ{YcWq50byZ&pymg%)IbUwBt?_IxyFJpstX*(+ z9!zX`Z7I8LxemnWKG*5R$nrJ`jKj?Y#x;|C0Ui+>%&)NZb zK)xPyk1{-6`K`}}U9;QoGMwEmTP>_VxgC=AT8@%^t(VImZ3tef*1LROe4eB0Pu)G` z1&Ximc^kW5^C~|cb$>mv0-7(sM##T>5Idi0d>yO-U9ab9tiWKg@4fqq88SbLlJG-c zt*91Qtip#RF)mOPIZO~Za0El*FzjWLku4H0ll6Rd2-0VOkGqnaALUqH_kV^j_GAG+ z6FL?Q>9`-rc#S<9GqgB(Iu7j0rO)donbWt8B7vT)B9V0G(ffyRK3fhn&+dtrL)O*n z?I^%mw~rTl{o64?k9}{!PBC>hHF_{vGgWFuCQA$A^~}-m^N~3O?&>ts)D5bFj0_Z= z6cYnY?H#VNev{qu9o4oy-|ybmXl>PZyjd>S&HA-xY0Vi;Vn?AoyaR?qc`biBU=nSN zmnRi%)3jLa!VZLOlF?#ZJdm=of2@ThJRLtEp-%X>ku{oKjpu68-MS5$)gx+l9(>D^pn|EMGQRU*ZxeO4EETUBWz zZ&m^$kuocZ{zjPiaCbWVf|TB$+WLi_*Ve-}5>Lg`o-b2qSzp#$C0qJ=d-4c%!y3F- zX;d6J^{NrF5`*M;e5soq_eo{7S<2_RX=6&Z{r5vO{_L7IJu0HQg>Mu5q5Lg4K+$!; zZ-&_iArvA$BkrNFG?x2b!$LX^gYeElSZ-B~U8*JcEmoVn^fj7y(i%(L$g;o;qq|qr zNv}=8tlJ(5fNzs=`eiM7TZp(cNI;6z(Z(RU>GV=BZiGDwhlN(jwG$>9F&n0th0f_H z_2wx#{5dbjcGYO*T;>h!(MC56@FKbEE$FQKx$ceV^-+*7pbIrgLYPW16D|-`IX^A zIY9s8(@?=yBDv{g$*D+<%51c^<_f`BQX*|QN|?`>x>CJ^UBVg@5{f$-Uv?6CWvu2K z1i@Pvn|TAc-j>_t#5hNOe6#mm(TCulr?)|*%GpA~inXvtxD32FX|>&RWGN|8Ip%s2 z**Fq9ij^rsP+TP(-Z~8?LLiBW0jZ9&w(H$+_51CYo!C>>)-w=nPTj796wBQR==;s5fZZydqbib4v8sx1}A%~bz zfkip}2%P|#27J~@SK@h1MVLKd@^+m;TR$t<6fp{|st%G8InF{hGI{T$aHL7JzX=sNZeHsAMRNR# zv!F}wr@D>EQ?Rkm)h&NE?`{L<*A!|dn*T2$l3FQhmzLJ*R3jOqmL5=S<|3_1P5L-W zO{kC1*~k^cKT93NBE=vRBAB*NMxYaaylHupt0BU4 zLHxnUKpl~EYw7W$RqZ7Mlc3#X zf*sFc{H8`6-L0axaB`lb?@v4Bj)X(WvK%oRDn9mFk<{( zr0Se1i&491s<=jbY9GOB0nnYWS*9?I|2RPPi#PMSpeumu+fEtbHJVI z2wYvEJwqCsY0WOiIo*QV2;4ca(EU+~b&UAHh^oP_X6{;sxVRkf)OGX3S>W$`b7Y|+gFtZL;+HR;tdf&eK_!t zC0rK3;Y5+lk*srg6z0vfL#HgnrcAOek|3V@!|W!mz(0&$3iz{MI}tuEq1FLJ8u@3P zSiA~B9*6?3?jive5Vjf3A|BgpW@^1FBzhqwmG>$?jiE$1`D&f47mc!%sTLXDfOrBA zmEvtk46`mrITX45G2AsO=XC>8^fRJJeqkfr^mvm((DktPRG9h@$rgsh6i6W*h%4kK zW|^1aUoG~mZM^yoa&8Z(1jR9<18d>8*n}*{$|tzkw{f&;VklYK3u7t$GF|se=Gw`z zFy5FSP+CFm63Rrjsh1Gbn-9Kt>^bJgOY(%oiE(hdNtBaWOrgE&GPU5!-yYM0!9>Ku zAp(>?dXoL?m>}ycXa}E@It(xnG_cBytrkRCrE(I@>Ssb~mMjfwVCvgto{p;oS7I-Znog=T^X_6LrxWEQ zu`vte?-jKNZaOsmwg(A4!fql}a$>J9wXpd~G1HpK5x#NeX=Xmr@^kFb0CS(--$ULm zE!%$6&5{^vRv!bUa1l>vQZtAz;Tho)WbSHH6^S419-mIj68-5;QB<@7$}E%+7jZ*E z3rr19l=YBLu}2SzLG?33We;A0Bbq z$YS8EmtVNPh`xT|TO`_e2%vhasNF7{fmSA?(8_~M7Ezh9v@Hl60gZ^6I%1kQlk+5p zWZ|bDvKDe??LH`TQTUaPxu>lQaPsT6sx#d*(fqhdKd+ohz$J?{2s*Q23OCu^q`=si z-75R3BUX>)8x|g2T@mpTakZaa4?ey0MNnj(bgMKybUkun3$jh(m9#kf?xDY z_4YV_l{;@PH0*>!tH1;uw1*qO&qYkp#MBj$Vi@_?QR-q$yWy~iS~NMk@6eL@j!LJu zT;_PUYQ&)6&Pp9we|&E>VnNGOvqGrN1{+{C5IZ`7$sIs&9a&s5^iV!Zcp``q*Dl%? zgB#T>d^cN0D94{ZZGT{VVQPx>UKTv{=mm=a`@Av{%*qjSY9vh?9PWa$$qTs zNaKi1V&qK|&dgUl(>_s$*{Z& z+lE<~K6EM}pcnJI?U7WH^I<>MhW9ouFxMKeU=?d(sFA{=50S0xbqt+>&9Pp-WQmaU zRx%`GB$mi_^<09;$&X?gd^D;=e3blg`60>IBJ~tN-X_I%m>OG4){f4@6VN0Ew#z-o z2M&*@jHT;fOrwya3Gt)Sg)N(DdeR?leq7+NCPVeI`!vklR?KZsbsfN{lh^+&4IoQ< z2^$mgXYEZSJeq2_n7VN>(-)!0k2b%3>kcW4|pKm{AUk(gvV{&y@|AbD-fWo$d0upAg>31l8P z%`dSEDMmk<B+=Dx8Rs+h`mlHLVL0`!xdgA7PV0-ox?)@MyPlHsRy(?mO1g>#>)wwr$y(dGl5~R*H$O{W+12 zRd9<%1nUSbMo&P=?W7gF5KgQ7FuI5=N_bkj*kr${@&3K)a)4y_WhUMnutv`99#uE< zcdzO($srAy6Gjx(!Rb$9v`uQhfu0`iX_zzZh<^6tGVUR7Br8-Iy#~W~LGxwY`V&>v zaHT&yX?fUlMoX?kp+Fer z@*sdo?FAEcI^4)m$q5-FQQNe&~lw2D2g@nqx4IMhA5&1Xc*BOB4;N->J^2MLEr`3H$K%yD&`Dav<*}V zzcvtmf5oE%%k|wlk%0=4GY_gHH~jRidc^F0R-iHHr>1GAa2wP@y^-4B1{XFb+wfQx z$Xk$~G)u>wPsTB?i#1|b`jkGqhZ#&JB zJMMCLB|P`31@80H(+w~ zP`k7!oER{9qsSe<)b^JxpGUd68r~m$i<#T=> zPYN(y3x@)y##nKh_Pkq|%RodzA)WLzNi>8a?x{?NXe;^U8YY3vU;Jn!m61P?y!Qj+ z4hZFQDJw`K`hHbiK-wq4-33nI}0zy2X?Kr`wHNb9NA`x~uw_RiMQN>>SLd7yr5* zY$H2XB39J_8P6uu)n~CRpQff+0^e;K=_r$0^vNBC2hQ?U;h^gQZ^3hL4@{@c5L4)2nAUXzLuCX8@8r<9%oF?UrzWuA=QNd7FzxGZVQo1K zov;9kBWB!d)@)2m+4^sde_lLoc;7?-ZQ;}uEH&~C+-VDpZzuAMA5A?Sb42=g?n@7U z7>27ha2s*vy$$lW5uxVNV#Z*Mej`%g#nV=;!pP2TFlf$y9L0g)(3p&#@}Q8F#gFwz z)eWa_jLm@==4wt2d^Xv9Y3Wzk2)PXQ1cZtm+nh9NZcdW8i5vvBTSu}o<`c6xH=MI0 zI6gGMUe&WbeWyYefq|r1y{z7Lo$MF53$m z7Uo#fJ7nJo2V$~S)D4VL+`wW=G^AJtmMSv3X{8tuyGvwD7A@(131x?9glD?m>D%+A zBIURL2#VqNJpUbRxoFdEjs!y0HV<1^RWI3MnKVgfLx)&(0okn#)<8uWXmg$)0<-M` zXqEgmA=eB$+bJBxunvt8aD+ORQZDLu5LKK)k|+$egv&>LmQyE$zd;C+x3?Aq60GZJ zu`^QcFQRJws4xXnU;pCff{j*-!iO`BbgbV)N3b^Hy)j(6K^R30iek%q$}< zPDs*x1)E6%F@*OJ?muH2dMUWG}nD=1;6iFa{0dPD^LH{NY_Qs+YDB$y=0YWj7GB!5(fVZ8DuPkDR|6A;9IsuzO|sigeJ$%yo#W!(Jyx0gHNsKpz&b(U721t8&f6R`s}$28Akh;UQ>=CvF30 z508BQx!DKedapz6H-%CpV7j=-A)^N%*<%=4rm-V2+`5g*%u463)CMMJkb7Uo4l zR=4{?}XPir$NGd#u9u+hiO7#PFb8K#jN?F)9FRIn7{eLLegeFG9!LeendmF zyaTfBXA=WqGQ}@gDc9L^SX5<@Gl|bvHq}mcx|5V~n8+TqqQ_O(KEyktWS;En|A%51KarUGmrh-`wAtkZV-8_h5)x z!p*i@GEBZnfsR<=SN|?|g7kK9UDLdzwW@b0M`)dVgCgLtp$?R z`>{pZcH3qp09KGa%+8KDm+MLVF<9qOva`U;@`8O0w+ia_SxV}Jz zV$J#S{Ph~SV=Z>f{xS0RV8rBhJv5Oh1Tb9R1J-6_ZHvpaeAq>jRhik3^&3w6yFHB;&&b0LwF$01Td zfJ3IdH1|^_Sd*w}P((LJF=+h|bl^*~*Dta*+Na|*XkEQYaL$UfHoOa4VTa08b-(lJ zrFC#q7C&M!IBP||09)|y8HPFr^+b3jEnbj3+LAX2%aQWl*~q%dx#NUY={AH$_ZxS{ z0ISaF3Nr$FXKq#P(J0GgBU&AOWm;_lr_mrxmnn&uFb4cx3W3z$zKcM-*-q!*c40H1 zxXJ7;m;o(}JTw*+VG3K^`dc(VXy6bzc;5?fZQB?en`hV`L%?vhKU%(1)-|e4K-<6E z=k~Jzl!K-wxp|&i(l(T!B1znbHXuXYWdWJ|7idOkGHJY&>aaOn#}ns2L_pSz_*vjJ zoq92(XFqPEi|xhS9teS`xqCxdi^NhgXRA=dK{Wt|)|cH-C(rB@-bXs2jiE{bjU+wmTc8PId#a$g0El#$ax+ajCQ1*}+C zqXk=gMrmBJF~Y35ZY&X`{5<*2u@iFW-?*;h59_6O$NH~6pONh5Q=yLa=tY*J^=oo; zTQ$^bBIP7;EW4bhMWaWa3D@A2=-PrX7cZz|M#M{{SYtDk0Q#Sge`6}$ z?;=NsxP=bqQ*PPLGBlo{9RX3f>iTligXA!s7#R~98$oimbS@O;JMQgp2qHxyL3>BL zZ|&n>=c6KkYLr~jn&&Re_P$t>J3D@R0_A=?QOe;lM9gf10bQmI*(+-e{BRl_fMgxj zIOC++&`bzdd4ey}-(d1~+7lotb{spTy?w}eJTbBs!7>g$Z!5Z%J(9gKYtry2AXKS| z+&PVSekf6zTaGF>fW}z1@CZ#|DsWxxTRZvJTRkhGbq%dimZJb_18Em!%<4P_C)>4S z?}GK#%!O4siw%axGz);=o8+qaLE1Rm?Yn7SuD8laKsqtET6o<9MKo)pe(g@@RrY-r zO#vF@@p*^N{u0(&Te9YD%=f7}2lP=1JM0p~(Zpb#<$=xjL%2`ju*(wYOa<2zqi3T( zW#cQ2m;Is51F|cHy;gzzAgOLJW01$me8k2Z(7o5LsDybY45K@anARB1qemQhZ`1s3 zvSP;-7j4c3MkddolSO1yqblFp3t+#qs)L;D$fZ=23(9IS_8ZawP z9P0bNDRY=5hR%KzK@(KK!-EdgBi3R_0x{b5@L46@fVeTXB}1B#D}eA$M4Sbt5P~+0 z&jJ?)nZfKKWc4-Q`~CUg=N-O`Sn^^HLNa#GI4a1T-}MaQ z75J5g47>`P5%)QXi~G8ukowK1lv|-@U*3EWPl^+;j3ptmSiE|z&Y3!+5 zd|r{n4>w_UR^f81$GN;CH~5mJ_Z7CXj^*i!lR;Rdg(qgR3VrbNvh5RWodyZ&xQki@ zs_k_32UM%71M#xSusStd_&15(%T*Nkuq?tdc}EgbKW4I&L&WJZn_%Z3wEo z3L#`gy0(R!O2FM08PlCKp_P7h@1rzjGFJUP#oDI?Da71#f~G1WoJV)1sTqdRF-)Y- z%I{QJTzAD75+h(R2t_7M&YZ${!Knw#H|6HZyh$M-)BuBbZfE7ocA{>pw0~(8E4P2o zKRk8BCu{r^a=_$(#e?e`m};k%lnqVOP|u|My3j9R$v`trP&fd0N0;3^Kc_CA>ry`J z?Zg(N8w^nQ6t|zX6d=_LMTk$o;sOBQ_BAdmsLu8 z6U`mIEV^s%b_M1JH>*n`(rnf%d4ypiR-}{KZX`4hp$Rw*v(O%49XF4sEIxT*h@6sj z}}74WmWk1o|4S6U>0NHAl=W>UA=A;4WEWFswgq zx?b}JF`KB>{Ypdh@-vnpGjQU_fy`uyVcs(1b>(eYxr`a6j%g3gdgJ{2mAYQ?$&(bo zk#frR7Gx|D_ikstjP~lTDxMlVS7yPSo)eHGJ{S4)g@noQorPW+mV|ltBeQ~{P%x4r z-zX(x_7mA?Feevo7&eZ$MoK3vXRnXs25G+UXI~Lj7z?^|2+t9(zShuDQ@Wvo_F&LFAEqhAG)%1!MqA+IV=I%UCe78k&b ztS$Ru)>}J>;bE1Vr9@3G7gJv5FRK}y3pBE;H`OI;h3oa;p&{iy6e_=Ms60wwcriev)?`iU$!K73X1m+~T9KBP0we z9=~YX&RW&c%fDAS?d21#gk2ta0*1wp)vlETxyU{P?|Jtekz7Vwt-NGf(}D_?8et6G zJTn_x=c3BfQBQX_Q$geM2r3tc(9MI!yOjP$iB2DcKp2!ZD-A^sEM2>h`K~?6y$3ofFQk_%{cUzxLZ3kK_`oxg9MJ< zR$jd|AkF?#o_JMDe{j&8Dw;Xh*QaB4UZFVsh|Ovk>toy4O*EW2y5p z;FMc(-oZ70lps-`&nF)l=q7&5vYfj7Q>Z!Wa>UDvL>fPR2asDnBRW&&Xr*XX(8mEc zr3?`ix2mk}Z-{B*2X`og!dk1p=@5?$d=fj(Sgc(rjf zxPzexc{L!;dzObch9*!Cad_-@;`?{B<`s$Kc3cgV#`y1P1 zpH4~2^rEMGZKO22m5~WLJ*8nUz75-kwzxP+N>X zGyfR*BGDxX^hK0R`ui(49vrNxTp%AOGOKo?y0l%(%K@J0v3&T9Baaie5Yv5FsSVPd zrnbB{?xWOTZ?fwiT4bD~1VP;%nXQ}Q(+|)k$W^<=zf7+laVhlct+*-I)9LpmC`y}+ zjqoK6@sO~(`86nR0k|NyNvN!r(OUE&Il`eOWtjBg5u|YY?$l2xuX+6jn6cZe$M`Fk z^qG&n(=G4YZDY60w|nMQ4s8n@JM(}r*#=lC|GF4B7|JbD2YcXJYJ5=bT9Vxa@=s$s{Pj2m~eM`*h9( zoDoT4oT#C(sHHfPKo-|4w~JT!DEM{tx0K4EsVWe>4zITB)NbD|=f9P}CP_EFAVkY@ zx7TptK@rGnTQ%0J)m*9Xv?DlnW;RlIEuGGZD)UN7lM*q?>o7z*NK(N9ZHtiPaYAx3sWtgpl zm?R~KNsv{=0mKtd04zOU>+_5rz%sqOBI zRGY41ax=1l;3G+8m+j6`d8IS`v6Td1K~Z?jke1QtK-1;Sw{r?Wao7E}_H@_XcAtWe(FKUquEg)bUM0@*q~%*{FDL{ zcJ9$^2DR0eUPo3L*aG&JXMet90;)59y;EL|tLtRx#V-2gSd{tm9MPmI9p-SFv4>k% zdy0`t#UfcdsH~fyGGSpBWZ!kxuLnj{U%YO1vT8WC@HSyhCUsr@F|GSD0FC!x#xW>sTB#MY$DwHb1lmt1esSJtb1Rm0@f zz(lB|3&zWebG;*YDZ#ClYS-oX)Ubm<$}|bmZR853!P=S4CuaQ2Y8na?@$%^$Rov%W z-Fywv+B~~DBq__uh`SU#ZgvWGnWp5$U#E2)XAU7aXK!_K>^vk&7PzlO3K^prQzb|? z76z$Z&@?{c^4c8^KebJcOOJu#F@*w$Te_|z1WOiBhWWCUu~ zn;Moz&D9`yO_$vmroy`0CFQd)^Wqm#&PRambu!01Rmb_03nj+p&)kkR)U!1NGEiH< z8bTICUvBi@{r44?w+Ke0Hz#Tp&$6#j)T9Qmj8J9{kKtPqy{q!@>XuTp-}P<0HKH|dtusp7M zq}#*Ks%mM$s%?_DPdaCzGbkvZtmxU%0uG>sto})nY`ushh)IkYdXj&aY$pzE-R{tP z`eOF4y1&GQ7cgzDN?ffJA*@j$X}q}Zu!Z)C+#=1Od9^?7T(WXJXiP2X-18>Z$0ay+ z@Nd(Op3m}^&=b*rHeXj%z>kLhZMts!E7bp==4)G1fT4*Yz>vY}Zx^=dL@i4!MV3fz;)T^*Fk5(uy zjz|DhvaJQrU2`!s%F22VHmL=26->t#70Y6plEwjfWCzc&4E1FyCh1(n5-g0#xo5-=+;GP<3+-3Jn>7s!92Q77Rr#eoL_QDg!E!Tu*2a~De+6X$=okXLH8 z|NbtCQ=O@KzGyT+G6G^rSHpzrseKhn9yIm4KK2}~35f`$#I4D!sx|e}u_2s)hg+czKmhx5&iiv<}em$^w-H`TAM>awoktMV#ALe|G! zq*GyM4?gbCoquuco-04k z4_855YGQ3k5v*()x;1Gvo_&6}qO4Y$KJ0%=fSyhbMY;-9r*pr})Aq{^zaF7`YOu@c z6x@K56}s~Ms?11|*FtPub6ex~1g&I!9Ugk5BltP|cZ>t}Rvd z+Lb1tGXxp&aZrvu#cCADswJGmA}nQ`V7gc30sr75sms5Bs-#Ja;G?S;p;trGV$kqM zhi7j!w8wqrsr<5WZ*RUWz3FUzecsjGt910{p^>MH;ptc4c_Wj66j8(U7-y_~AhU=j zW5gI|FRgLIy$Dol;_ri-=RXo~xshXT8Xhok$V`xl$wUbUX?Ve3rbk;4lfm^O?&QDJ zy4>V@e-~iNl;qv|+xSmbqV??`K$*5&Kj#P0(F#&s&G^+2-`)|m!1Zb5Rwh)Hy!Y^5 z;;Y2I-0)r`a{4VSjpRK-nY6G|WaIN$fBnKBq=Lt-+z~&RkNxGKWX)(FYhPI(#;-v~ zFg8ZneE7=(*^uS75`9Tyst}QI8R7tq1>Miy5KvQ*;YAGsVN6bL`}0LeO_;c&r`GxI zw;uFQ9p2xjPDK@7rJ#1f%}d7>Uf@!+Ew>yNx^thgudc#e#i1F!J^+d_;+|IIPzEojVsQ5!r;_mbdihHw7y8j z#OY}rB#0AI;e4U81jt_hB6WI1D!vQYgSv-mtt;^&rkzPMK#-atCfY&nL$Upn$lg+} zE(2$mgSqUlA;bIvqiGT3Q)BY1q1KfvWZ2p)Gg^1+e;%Be0NJ?0{{<>Ue7WRE3w{K# zry$bg2hj?GHZfj&4Z?|VJPud&kM0Ye zLR?ZR*7$*~1-!Tkar8;IB##|rgB*_g7budU=+aUXjWBsrcYw5y#1Nsc#`(v!wgkM} z|5Xos;f-Qu)bG9Z2kF9&s1hVWI>$0q92u89Uep4IDN_6O8Sdlv`6s91BVR;J3RNfn z+djnIwBLE*@}@Nd>r1Eyi8L8*0F4pC*qEdsY7xTNOotb;!eHSIDo%OD`*xrrIwbk@ zwZc7%SX`|H?6|7yr@u|CN$eJY;VVB``~^x@JUn9k?~bZq`{R3Le=QgUbwYs6a=MUr zlO5aZ!)M?U<6EGoM4O!Db$oD6+0(D^uc9lv(|1N>@T38{ zvHt=kMM-W^<{P<01jbTFRHlz^$TU~3q)|KXo)s> zH9~i{)@(ikOCom#p%-MH^;NpQHGV4|dX5cYy-m#Lhh_3377COdqcoA-A^QAH!^TL2 zPtYW}&L;k%AW~E&AvK7VI zO0ke1lKlR@XMq4$4yS(?T4Dl{Ni2_agVyQBGn)8y1U9Uch4dGh$&9@}_yoko*Wb8| zcSZ?xItNBZfk|@B@)cag>C0Vc2%NZ`6k>muC`v_EY!jJoqQK z%p@0yG`Xy1c?F~&N0!Y`Q;nG-?bsgYpGB4pk%O<4jF$vVN{mC|_*DsiT4AcogWSmw+q(6MDXkTAG5P%hM`-%QInF!%$n8c(-@7ud!d*s4ulyo7h zXG`hmi+VxivLUw3QBygKL4(jpiV(o_9Ld?GW^#(Q@ng<1ATbv-iSbK>^pRDh5g;uh z*D}&o9uC=vAt^112)=BE^;d-Y5i>;=5Zn+Id8J4>V1C6eh)J@@-tby6|7DCa*5e)|pm#aG;*FS*}B7 zV%n|`PU2KKHfl5+`MrE0`YcZFwf#Pyv=z7O@p$~OwdiP+xohehK`(&sv*1`JT%dg2zCq@5Y@0HH*n3tE?jeX z6CF?$wdSN2Vh%7R*q+QY+@>8Y)&BWGujS6?kHYKZ5QO{i4qn3y6nSHPe4BmjBm8bk zI1qp3lYVj0OpB{K>?4fXzqZC*&cjEG$5Yw7;nWw z#IvEbgs_fR^a_JFOU&r_HHKPckla$R*}h0xhjP-)EgdF;D;^X|2@wT_k{%7AY@?mr z?$~-qpKw3&F0p=}WHMjgED&ApY3qFN0aQ7QG?%;)Z|HpWI5&KXC$tXE=CZu0zoe-4 z{%Au7SBATSY@V~UgYfjV*Pi-Vd5AastR8L6sI;rANP~OzoXwJ(Dla0Uh|!hb_Dk1xgS;v zJ3Bd+S2~$k-o=^rYqK>GXPWIp846^jpkM?~7)E{F()|FABsvVVaPg9O-K`Ts`BOWk z%qD9m%m&4jH+s)B#G>NK3ptq*wekdvCi(JBjqh5FGi4q8k3TcQjwffO#i5!zcwr%< z5W;tl2g;B4qlg^WYHlbOXBVcAXQ%|zw0L>>GtR!OkLp~P*Bds0z(Zv6Alx>VybV^trc zktct#0(vRcS)!hKYe z^iJ2t62`q#$`OW{XTnS0q$qchI1n}*+@{d!p>fO`-sq^0(CcLLk?v}E#M=Ue`96ur zHg`51!uto7CiBr`!bK)%9$TN3Ho_u38?ONLUmupsQ?EdJF~^lo+VJe&#mq$-wMdu% zM=y7>57oy7C=~v;`uLQLtB%uRr0$33^x~_`v%719&(0Ii9-ADkwAgB$9lgv@qU$We z-{$9>HqKVQucHcrug`CcpXFWj2p2vx^A%|Y!y2Yt?FSREN>t}D24U!Ln|Cvr)EV`Hk{!Jq){|d?fkWTy~ zDU$nFQWUh){zJzV6hhboj8m@%doEjHUL)eql9bey9Uc~gj{GKNeXob8y%zBlo*puu ztAKn&mLw5z&mZi|h9oAfyJhPF_>K&!-|!LzaH3Jcc_-076VHG3mja0(OWBhz8k5RS zONeNgj#s(odZ#|`KU&1kCgNl@h)lmg=Aix)L3LUW5#BctkRV795S)J^u&{In*gLuZ zJ0ohG@3cl2#)iChB!O9F6KQC`0{1?8BcNl$u8GYNflUO{yrQ_709M(!l0~QuL!f+t zk-(B_feY6#OVObRvEk12^-%<)ppe|aH8|eKv@0`sMy44>u9_S8@UdQfBQe=FX%&kY zZ+>%2lx)UI3ar+1`m7-LX5hgZAi1HKcPbPM0pcd+Q2ATWlprXQg(u6eKdVy-!<4c? zp5Sx932uyWp{;+Npq%&#$;fZ;=VD$`yolPx@*LOjS*>yzdk1=qBoemxd_z% zO2`Z|l6^NIb%c5mW|!#?iiM(oiQK>4t*5jWeR`zh*biiY9Um-ZDp}j`C@KL}jAKCr z$pHOO-ZBl{T5#(N9Xvgvd)9&btZUN3Q&c#rmbOs$p`iIvYek8GD+9%msVwE7!MkXl z1p=NVL%I$YJ8e%bLgXD8X0Z1dGI-!aedp+-&CW2j+K`$B9{Gue2{?aeTAY!_49PVK-W=#Z4lvb}!QRjKWb z0{+;#5e;Saz9`k~cJi>}yj(BWU*^oh4Hm&RONoOA{&Lvlo{y>I76;fX6Yq_Vqb>o{+CZw{T0&xX-ECTDTERGh>?SL+B;fI zL`ks!?Gz~?eQc=OO!T(j5!{l8g5i?yJ{jx#p(O3KW;R`IEZj2scpljFMMNSYgLkA~ zpflZkGxJrDC&V5~aztBME6#*-3kh3L_^&Dx<}zLKgD?t!#8FBce!WAE-_yO|m#=<@ z>MLb9%Tag7>`*ES{5ORE``>ije?qXeH<7monEqFt;J@|$>qqqs!X2Z3L9hNRWdEby ze`1djhV5fQ7rSCs7bBkV_?=)FnL#%6$E zgog-?Ro@-v{tnNT2fOiWy_cZ;vv;yJ zv@n>USeCB?lOi`g~v0pp&#SF89X8QX?n~H(`My~DVIh~CqSU2TPDI9RltKR=#OL5-YHR6Y`)}g2HZw3V_&xo@P^yh< zP-MmTQOF6HQLKq<8<&(rE6$HQKp2|Pnp_n9M07NQ9Q~vm-4A+tdidx+y2QViA-K+4 zwYfiLPFQdF?hqwN&zU*EU(qc5sACOnY|ISId-rvo;9-^g#{R5`0iVLfKSQ>E)xh< zsePRBafV+QV8FMSAAoqUL|hmx0?nPlWgV>a-B#ABIwop-hZtc`f`#q4*s(yMjasKm z@G!VTwyUURYW*6G>K~O!)<1LXEq|@|zm@>3zNFDpYH@O69&q+0BNZHnOV1t5z8t`C z{NwhA8=P(Uzr51DFn#(H)yhK?o~ZgxciML2+ASrIS<}rTcQ1SEeL1xv4`KkE)TRQ%djQMBPzUQB*nOfR!s zbKcB9ciwQA>Tl7w6K8eiw&Q6z*Og+cbTvNQw0P>IJ6Dr6WZN6txn-{tgF|-YJhl70 z^O*^Iul24As;^G8{^gs$OF93-#kSeA7aVHRVbFffm@H^)^q|(kUT*D_{&w*Nm!I>m ztFy1X-*K1w!Zp9zMHB4ux8L%*bN;BKmDamsd_1nqt^v#6{ykc@@Y%mjPdoeOeNnO% zn^Th~Ht+VfD7$;H5p9*X(>>--oOfkKe_L;p&VQy|7sJmxoImIIUT)vdKZmMXpDe$A z&f)thk@TBW?za?J`A3${c_P@~Tp4n<(nRu;!h@?e54#i2Y-aVolr`hs!Mi3i?<`uC zDJZy7$Vf%{rpJ<&4y8pQN3B*Swrq5(pRJdAa6Rq`s+r{)rk?Gkf|Bt@jyGQ8%qrWVuS+?NL6)y>3 zgx3HgTm^T8qomrcfv5cr8wdaw`yOwYHK}EiwvdoeleBots@}bsYpq_NDs%FlT7Ueu z+oOxhk5bN_%P6y{X4u|)tgzu1!>P#bCYFNx6U|@UeEsDE*DJBy1w0qrKJ`poz*bh1 z8TQ7;Y??wdU%CE^vw~MQyp23+;#<>xt;XltFa3AafG!6&^AQbvBFEFjx4|)OVVPsGM zlG;FwazYH2V_y&sftZFqa{)90YT63mVKVqkLrFpC#-dLlAdD^3Lo*g>LIK?r^qvF4 zl%rPIO~LCZ^kzKBILKi+?bc|M%H>^PBK-_f&r#Uzr21y&}tsI1bGwjhEhMeB (entry.target as Webhook)!.id === channel.id)[0]!; - const auditDelete = (await getAuditLog(channel.guild, AuditLogEvent.WebhookDelete)) - .filter((entry: GuildAuditLogsEntry) => (entry.target as Webhook)!.id === channel.id)[0]; - const auditUpdate = (await getAuditLog(channel.guild, AuditLogEvent.WebhookUpdate)) - .filter((entry: GuildAuditLogsEntry) => (entry.target as Webhook)!.id === channel.id)[0]; - - if (!auditUpdate && !auditDelete) return; + .filter((entry: GuildAuditLogsEntry | null) => (entry?.target) ? (entry.target as Webhook)!.channelId === channel.id : false)[0]; + const auditDelete = (await getAuditLog(channel.guild, AuditLogEvent.WebhookDelete, 0)) + .filter((entry: GuildAuditLogsEntry | null) => (entry?.target) ? (entry.target as Webhook)!.channelId === channel.id : false)[0]; + const auditUpdate = (await getAuditLog(channel.guild, AuditLogEvent.WebhookUpdate, 0)) + .filter((entry: GuildAuditLogsEntry | null) => (entry?.target) ? (entry.target as Webhook)!.channelId === channel.id : false)[0]; + if (!auditCreate && !auditUpdate && !auditDelete) return; let action: "Create" | "Update" | "Delete" = "Create"; let list: Record | string> = {}; - const createTimestamp = auditCreate.createdTimestamp; + const createTimestamp = auditCreate ? auditCreate.createdTimestamp : 0; const deleteTimestamp = auditDelete ? auditDelete.createdTimestamp : 0; const updateTimestamp = auditUpdate ? auditUpdate.createdTimestamp : 0; if (updateTimestamp > createTimestamp && updateTimestamp > deleteTimestamp && auditUpdate) { @@ -81,8 +80,8 @@ export async function callback(client: NucleusClient, channel: Discord.GuildChan name: entry(before["name"]!, `${before["name"]}`), channel: entry(before["channel_id"]!, renderChannel(await client.channels.fetch(before["channel_id"]!) as GuildChannel)), createdBy: entry( - auditCreate.executor!.id, - renderUser((await channel.guild.members.fetch(auditCreate.executor!.id)).user) + auditCreate!.executor!.id, + renderUser((await channel.guild.members.fetch(auditCreate!.executor!.id)).user) ), created: entry(Date.now(), renderDelta(Date.now())) }; diff --git a/src/utils/log.ts b/src/utils/log.ts index 87549dd..c6416a1 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -72,8 +72,8 @@ export const Logger = { yellow: 0xf2d478, green: 0x68d49e }, - async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable): Promise { - await wait(250); + async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable, delay?: number): Promise { + await wait(delay ?? 250); const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map(m => m) return auditLog as Discord.GuildAuditLogsEntry[]; }, From 94ff6deb29e8ea2deac40e7ed50b327301b4ff9b Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 22 Feb 2023 17:47:26 -0500 Subject: [PATCH 55/74] big work today! --- TODO | 3 +- src/commands/mod/ban.ts | 4 ++ src/commands/mod/kick.ts | 4 ++ src/commands/mod/mute.ts | 3 + src/commands/mod/nick.ts | 4 ++ src/commands/mod/purge.ts | 2 +- src/commands/mod/softban.ts | 4 ++ src/commands/mod/unmute.ts | 4 ++ src/commands/mod/warn.ts | 6 +- src/commands/nucleus/premium.ts | 72 ++++++++++++------- src/commands/privacy.ts | 6 +- src/commands/settings/logs/attachment.ts | 5 +- src/commands/settings/logs/warnings.ts | 3 +- src/config/emojis.json | 2 +- src/context/messages/purgeto.ts | 2 + src/context/users/userinfo.ts | 2 + src/events/guildMemberUpdate.ts | 3 + src/index.ts | 5 +- src/premium/createTranscript.ts | 2 + src/utils/client.ts | 3 +- src/utils/commandRegistration/register.ts | 5 +- src/utils/database.ts | 85 +++++++++++++++++++---- 22 files changed, 169 insertions(+), 60 deletions(-) diff --git a/TODO b/TODO index 2a0d7be..3316dd2 100644 --- a/TODO +++ b/TODO @@ -1,2 +1,3 @@ Server rules -verificationRequired on welcome \ No newline at end of file +verificationRequired on welcome +!!Premium diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 0ee663f..e8309fb 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -5,6 +5,7 @@ import keyValueList from "../../utils/generateKeyValueList.js"; import addPlurals from "../../utils/plurals.js"; import client from "../../utils/client.js"; import { LinkWarningFooter } from "../../utils/defaults.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -133,6 +134,9 @@ const callback = async (interaction: CommandInteraction): Promise => { accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)), serverMemberCount: interaction.guild.memberCount }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` + }, hidden: { guild: interaction.guild.id } diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 9f1ee41..059bdb2 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -7,6 +7,7 @@ import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; import client from "../../utils/client.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder @@ -125,6 +126,9 @@ const callback = async (interaction: CommandInteraction): Promise => { timeInServer: timeInServer, serverMemberCount: member.guild.memberCount }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` + }, hidden: { guild: member.guild.id } diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 878d696..c795456 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -338,6 +338,9 @@ const callback = async (interaction: CommandInteraction): Promise => { mutedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)), reason: entry(reason, reason ? reason : "*No reason provided*") }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` + }, hidden: { guild: interaction.guild.id } diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 8fb1cc1..97f783a 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -6,6 +6,7 @@ import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; import client from "../../utils/client.js"; import { areTicketsEnabled, create } from "../../actions/createModActionTicket.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder @@ -165,6 +166,9 @@ const callback = async (interaction: CommandInteraction): Promise => { updated: entry(Date.now(), renderDelta(Date.now())), updatedBy: entry(interaction.user.id, renderUser(interaction.user)) }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` + }, hidden: { guild: interaction.guild!.id } diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index cabcb1e..8d49c3b 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -309,7 +309,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; log(data); - let out = ""; + let out = ""; // TODO: messageException throughout this file messages.reverse().forEach((message) => { if (!message) { out += "Unknown message\n\n" diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index fa9f11b..1b404c9 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -6,6 +6,7 @@ import keyValueList from "../../utils/generateKeyValueList.js"; import addPlurals from "../../utils/plurals.js"; import client from "../../utils/client.js"; import { LinkWarningFooter } from "../../utils/defaults.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -135,6 +136,9 @@ const callback = async (interaction: CommandInteraction): Promise => { accountCreated: entry(member.user.createdTimestamp, renderDelta(member.user.createdTimestamp)), serverMemberCount: interaction.guild.memberCount }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` + }, hidden: { guild: interaction.guild.id } diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts index 44f807d..8562c4c 100644 --- a/src/commands/mod/unmute.ts +++ b/src/commands/mod/unmute.ts @@ -4,6 +4,7 @@ import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; import client from "../../utils/client.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder @@ -113,6 +114,9 @@ const callback = async (interaction: CommandInteraction): Promise => { unmuted: entry(Date.now().toString(), renderDelta(Date.now())), unmutedBy: entry(interaction.user.id, renderUser(interaction.user)) }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` + }, hidden: { guild: interaction.guild.id } diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index 35b5400..ea4f084 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -5,6 +5,7 @@ import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js"; import client from "../../utils/client.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; import { LinkWarningFooter } from "../../utils/defaults.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -124,7 +125,10 @@ const callback = async (interaction: CommandInteraction): Promise => { renderUser((interaction.options.getMember("user") as GuildMember).user) ), warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)), - reason: reason ? `\n> ${reason}` : "*No reason provided*" + reason: reason ? reason : "*No reason provided*" + }, + separate: { + end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified` }, hidden: { guild: interaction.guild.id diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 354c303..026984d 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -3,28 +3,26 @@ import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import { LoadingEmbed } from "../../utils/defaults.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("premium").setDescription("Information about Nucleus Premium"); - +//TODO: Allow User to remove Premium const callback = async (interaction: CommandInteraction): Promise => { await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) - const member = (await interaction.client.guilds.fetch("684492926528651336")).members.cache.get(interaction.user.id) - const firstDescription = "\n\nPremium allows your server to get access to extra features, for a fixed price per month.\nThis includes:\n" + - "- Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n" + - "- Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n" - if(!member) { + const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => { interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Premium") - .setDescription( - `*You are not currently in the Clicks Server. To gain access to premium please join.*` + firstDescription - ) + .setDescription(`*You are not currently in the Clicks Server. To gain access to premium please join.*` + firstDescription) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") ], components: [new ActionRowBuilder().addComponents(new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel("Join").setURL("https://discord.gg/bPaNnxe"))] }); - return; - } + }) + if (!member) return; + const firstDescription = "\n\nPremium allows servers of your choice to get access to extra features for a fixed price per month.\nThis includes:\n" + + `${getEmojiByName("MOD.IMAGES.TOOSMALL")} Attachment logs - Stores attachments so they can be viewed after a message is deleted.\n` + + `${getEmojiByName("GUILD.TICKET.ARCHIVED")} Ticket Transcripts - Gives a link to view the history of a ticket after it has been closed.\n` const dbMember = await client.database.premium.fetchUser(interaction.user.id) let premium = `You do not have premium! You can't activate premium on any servers.`; let count = 0; @@ -33,19 +31,47 @@ const callback = async (interaction: CommandInteraction): Promise => { premium = `You have Infinite Premium! You have been gifted this by the developers as a thank you. You can give premium to any and all servers you are in.`; count = 200; } else if (level === 1) { - premium = `You have Premium tier 1! You can give premium to ${1 - appliesTo.length} more servers.`; + premium = `You have Premium tier 1! You can give premium to ${1 - appliesTo.length} more server(s).`; count = 1; } else if (level === 2) { - premium = `You have Premium tier 2! You can give premium to ${3 - appliesTo.length} more servers.`; + premium = `You have Premium tier 2! You can give premium to ${3 - appliesTo.length} more server(s).`; count = 3; } else if (level === 3) { - premium = `You have Premium Mod! You can give premium to ${3 - appliesTo.length} more servers, as well as automatically giving premium to all servers you have a "manage" permission in.` + premium = `You have Premium Mod! You can give premium to ${3 - appliesTo.length} more server(s), as well as automatically giving premium to all servers you have a "manage" permission in.` count = 3; } + if (dbMember?.expiresAt) { + premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server` + count = 0; + } const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); let premiumGuild = "" - if (hasPremium) { - premiumGuild = `\n\n**This server has premium!**` + if (hasPremium) { //FIXME: Check how user applied premium + premiumGuild = `**This server has premium! It was ${hasPremium[2] === 3 ? `automatically applied by <@${hasPremium[1]}>` : `given by <@${hasPremium[1]}>`}**\n\n` + } + + const components: ActionRowBuilder[] = [] + if (level === 0 || dbMember?.expiresAt) { + components.push( + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel("Join Clicks") + .setURL("https://discord.gg/bPaNnxe") + ) + ) + } else { + components.push( + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setStyle(premiumGuild.length > 0 ? ButtonStyle.Secondary : ButtonStyle.Success) + .setLabel(premiumGuild.length > 0 ? "This server has premium" : "Activate premium here") + .setCustomId("premiumActivate") + .setDisabled(count <= 0 || (hasPremium ? hasPremium[0] : false)) + ) + ) } interaction.editReply({ @@ -53,21 +79,13 @@ const callback = async (interaction: CommandInteraction): Promise => { new EmojiEmbed() .setTitle("Premium") .setDescription( - premium + firstDescription + premiumGuild + premiumGuild + premium + firstDescription ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") + // .setImage("") //TODO: Add image ], - components: [ - new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Primary) - .setLabel("Activate Premium here") - .setCustomId("premiumActivate") - .setDisabled(count <= 0 && hasPremium) - ) - ] + components: components }); const filter = (i: ButtonInteraction) => i.customId === "premiumActivate" && i.user.id === interaction.user.id; diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index 8803e25..46784f5 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -48,15 +48,13 @@ const callback = async (interaction: CommandInteraction): Promise => { new EmojiEmbed() .setTitle("Link scanning and Transcripts") .setDescription( - "**Facebook** - Facebook trackers include data such as your date of birth, and guess your age if not entered, your preferences, who you interact with and more.\n" + - "**AMP** - AMP is a technology that allows websites to be served by Google. This means Google can store and track data, and are pushing this to as many pages as possible.\n\n" + - "Transcripts allow you to store all messages sent in a channel. This is stored in our database along with the rest of the servers settings but is accessible by anyone with the link, so a leaked link could show all messages sent in the channel.\n" + "Transcripts allow you to store all messages sent in a channel. Transcripts are stored in our database along with the rest of the server's settings but is accessible by anyone with the link, so a leaked link could show all messages sent in the channel.\n" ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") ) .setTitle("Link scanning and Transcripts") - .setDescription("Regarding Facebook and AMP filter types, and ticket transcripts") + .setDescription("Information about how links and images are scanned, and transcripts are stored") .setPageId(2) ].concat( (interaction.member as Discord.GuildMember).permissions.has("Administrator") diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 8331043..c04c7cf 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; @@ -38,6 +38,7 @@ const callback = async (interaction: CommandInteraction): Promise => { new ChannelSelectMenuBuilder() .setCustomId("channel") .setPlaceholder("Select a channel") + .setChannelTypes(ChannelType.GuildText) ); const buttons = new ActionRowBuilder() .addComponents( @@ -58,7 +59,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const embed = new EmojiEmbed() .setTitle("Attachments") .setDescription( - `The channel to send all attachments from the server, allowing you to check them if they are deleted` + + `The channel to send all attachments from the server, allowing you to check them if they are deleted\n` + `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n` ) .setStatus("Success") diff --git a/src/commands/settings/logs/warnings.ts b/src/commands/settings/logs/warnings.ts index 24249a2..84772e6 100644 --- a/src/commands/settings/logs/warnings.ts +++ b/src/commands/settings/logs/warnings.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; @@ -27,6 +27,7 @@ const callback = async (interaction: CommandInteraction): Promise => { new ChannelSelectMenuBuilder() .setCustomId("channel") .setPlaceholder("Select a channel") + .setChannelTypes(ChannelType.GuildText) ); const buttons = new ActionRowBuilder() .addComponents( diff --git a/src/config/emojis.json b/src/config/emojis.json index 35743e1..ecf1858 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -28,7 +28,7 @@ "REORDER": "1069323453909454890", "NOTIFY": { "ON": "1000726394579464232", - "OFF": "1000726363495477368" + "OFF": "1078058136092541008" }, "OPP": { "ADD": "837355918831124500", diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts index 4627bf2..02d5d2a 100644 --- a/src/context/messages/purgeto.ts +++ b/src/context/messages/purgeto.ts @@ -5,6 +5,7 @@ import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuComma import client from "../../utils/client.js"; import getEmojiByName from '../../utils/getEmojiByName.js'; import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from "../../utils/logTranscripts.js"; +import { messageException } from '../../utils/createTemporaryStorage.js'; const command = new ContextMenuCommandBuilder() .setName("Purge up to here") @@ -185,6 +186,7 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { }; log(data); const messages: Message[] = deleted.map(m => m).filter(m => m instanceof Message).map(m => m as Message); + if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id) const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(messages)!); const attachmentObject = { attachment: Buffer.from(transcript), diff --git a/src/context/users/userinfo.ts b/src/context/users/userinfo.ts index 3b1a6bd..496f84e 100644 --- a/src/context/users/userinfo.ts +++ b/src/context/users/userinfo.ts @@ -5,6 +5,7 @@ const command = new ContextMenuCommandBuilder() .setName("User info") const callback = async (interaction: UserContextMenuCommandInteraction) => { + console.log("callback") const guild = interaction.guild! let member = interaction.targetMember if (!member) member = await guild.members.fetch(interaction.targetId) @@ -12,6 +13,7 @@ const callback = async (interaction: UserContextMenuCommandInteraction) => { } const check = async (_interaction: UserContextMenuCommandInteraction) => { + console.log("check") return true; } diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 5788f61..721978f 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -7,6 +7,9 @@ export const event = "guildMemberUpdate"; export async function callback(client: NucleusClient, before: GuildMember, after: GuildMember) { const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = client.logger; + if(before.guild.id === "684492926528651336") { + await client.database.premium.checkAllPremium(after) + } if(!before.roles.cache.equals(after.roles.cache)) { const auditLog = (await getAuditLog(after.guild, AuditLogEvent.MemberRoleUpdate)) diff --git a/src/index.ts b/src/index.ts index 3c132bc..12f6659 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,10 +13,9 @@ client.on("ready", async () => { } else { client.fetchedCommands = await client.application?.commands.fetch()!; } - setInterval(async () => { - await client.database.premium.checkAllPremium(); - }, 1000 * 60 * 3); + await client.database.premium.checkAllPremium(); }); + process.on("unhandledRejection", (err) => { console.error(err) }); process.on("uncaughtException", (err) => { console.error(err) }); diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index fb6dde7..88a8e6e 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -14,6 +14,7 @@ import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; import { PasteClient, Publicity, ExpireDate } from "pastebin-api"; import client from "../utils/client.js"; +import { messageException } from '../utils/createTemporaryStorage.js'; const pbClient = new PasteClient(client.config.pastebinApiKey); @@ -95,6 +96,7 @@ export default async function (interaction: CommandInteraction | MessageComponen const deleted = await (interaction.channel as TextChannel).bulkDelete(fetched, true); deletedCount = deleted.size; messages = messages.concat(Array.from(deleted.values() as Iterable)); + if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id) } while (deletedCount === 100); let out = ""; diff --git a/src/utils/client.ts b/src/utils/client.ts index b05ef5f..857fb1d 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -32,14 +32,13 @@ class NucleusClient extends Client { callback: (interaction: Interaction) => Promise, check: (interaction: Interaction, partial: boolean) => Promise | boolean, autocomplete: (interaction: AutocompleteInteraction) => Promise - } | undefined,{name: string, description: string}]> = {}; + } | undefined, {name: string, description: string}]> = {}; fetchedCommands = new Collection(); constructor(database: typeof NucleusClient.prototype.database) { super({ intents: 0b1100011011011111111111}); this.database = database; } } - const client = new NucleusClient({ guilds: await new Guilds().setup(), history: new History(), diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 1a19e2c..78e3b0f 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -111,7 +111,7 @@ async function registerContextMenus() { context.command.setNameLocalizations(context.nameLocalizations ?? {}) commands.push(context.command); - client.commands["contextCommands/message/" + context.command.name] = context; + client.commands["contextCommands/message/" + context.command.name] = [context, {name: context.name ?? context.command.name, description: context.description ?? context.command.description}]; console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`) } catch (e) { @@ -181,6 +181,7 @@ async function registerCommandHandler() { } async function execute(check: Function | undefined, callback: Function | undefined, data: CommandInteraction) { + console.log(client.commands["contextCommands/user/User info"]) if (!callback) return; if (check) { let result; @@ -197,7 +198,7 @@ async function execute(check: Function | undefined, callback: Function | undefin .setColor(NucleusColors.red) .setEmoji(getEmojiByName("CONTROL.BLOCKCROSS")) ], ephemeral: true}); - }; + } } callback(data); } diff --git a/src/utils/database.ts b/src/utils/database.ts index 239da13..e7336d0 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,3 +1,4 @@ +import type { GuildMember } from "discord.js"; import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; import config from "../config/main.js"; @@ -245,11 +246,36 @@ export class Premium { await this.premium.insertOne({ user: user, appliesTo: [], level: level }); } - async hasPremium(guild: string) { + async hasPremium(guild: string): Promise<[boolean, string, number] | null> { + const entries = await this.premium.find({}).toArray(); + const members = await (await client.guilds.fetch(guild)).members.fetch() + for(const {user} of entries) { + const member = members.get(user); + if(member) { + const modPerms = //TODO: Create list in config for perms + member.permissions.has("Administrator") || + member.permissions.has("ManageChannels") || + member.permissions.has("ManageRoles") || + member.permissions.has("ManageEmojisAndStickers") || + member.permissions.has("ManageWebhooks") || + member.permissions.has("ManageGuild") || + member.permissions.has("KickMembers") || + member.permissions.has("BanMembers") || + member.permissions.has("ManageEvents") || + member.permissions.has("ManageMessages") || + member.permissions.has("ManageThreads") + const entry = entries.find(e => e.user === member.id); + if(entry && (entry.level === 3) && modPerms) return [true, member.id, entry.level]; + } + } const entry = await this.premium.findOne({ - appliesTo: { $in: [guild] } + appliesTo: { + $elemMatch: { + $eq: guild + } + } }); - return entry ? true : false; + return entry ? [true, entry.user, entry.level] : null; } async fetchUser(user: string): Promise { @@ -258,26 +284,55 @@ export class Premium { return entry; } - async checkAllPremium() { + async checkAllPremium(member?: GuildMember) { const entries = await this.premium.find({}).toArray(); - for(const {user, expiresAt} of entries) { - if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: user}) : null; - const member = await (await client.guilds.fetch("684492926528651336")).members.fetch(user) - let level: number = 0; - if (member.roles.cache.has("1066468879309750313")) { + if(member) { + const entry = entries.find(e => e.user === member.id); + if(entry) { + const expiresAt = entry.expiresAt; + if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null; + } + const roles = member.roles; + let level = 0; + if (roles.cache.has("1066468879309750313")) { level = 99; - } else if (member.roles.cache.has("1066465491713003520")) { + } else if (roles.cache.has("1066465491713003520")) { level = 1; - } else if (member.roles.cache.has("1066439526496604194")) { + } else if (roles.cache.has("1066439526496604194")) { level = 2; - } else if (member.roles.cache.has("1066464134322978912")) { + } else if (roles.cache.has("1066464134322978912")) { level = 3; } - + await this.updateUser(member.id, level); if (level > 0) { - await this.updateUser(user, level); + await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}}) } else { - await this.premium.updateOne({ user: user }, { expiresAt: (Date.now() + (1000*60*60*24*3)) }) + await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }}) + } + } else { + const members = await (await client.guilds.fetch('684492926528651336')).members.fetch(); + for(const {roles, id} of members.values()) { + const entry = entries.find(e => e.user === id); + if(entry) { + const expiresAt = entry.expiresAt; + if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null; + } + let level: number = 0; + if (roles.cache.has("1066468879309750313")) { + level = 99; + } else if (roles.cache.has("1066465491713003520")) { + level = 1; + } else if (roles.cache.has("1066439526496604194")) { + level = 2; + } else if (roles.cache.has("1066464134322978912")) { + level = 3; + } + await this.updateUser(id, level); + if (level > 0) { + await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}}) + } else { + await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }}) + } } } } From daff8459e8ddddb131dbe58cef018c2926043e42 Mon Sep 17 00:00:00 2001 From: pineafan Date: Fri, 24 Feb 2023 22:10:44 +0000 Subject: [PATCH 56/74] Removed dump.zip --- dump.zip | Bin 19058 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dump.zip diff --git a/dump.zip b/dump.zip deleted file mode 100644 index c259f0e7e2d47f0739af5d8cfbd4ec1bc18247c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19058 zcmafaWl$!~vL+1f?hb>yyE_arxVyW&z~JsO4DRmk?(XjJ;_mLUbI#ox8|T}-u^o}! z{i8CXp32InGPAQ4Wx>GFLH>3139G36`{jS%5dJ<*Tx=Z}SuP3-{6#ula~E;go43`WlOb_wa8*H_}2!@l@M(8?5SV9Zhp zBD2AreC=;Gvr$+b;37B{;g%NyZ**CH{Wx5Xc&KE(uy{YwTT|9d*fDlZ4gr5GJAz`a zLn#b~N$}00BHGEhkbiVOO(1=KUcQ{YcWq50byZ&pymg%)IbUwBt?_IxyFJpstX*(+ z9!zX`Z7I8LxemnWKG*5R$nrJ`jKj?Y#x;|C0Ui+>%&)NZb zK)xPyk1{-6`K`}}U9;QoGMwEmTP>_VxgC=AT8@%^t(VImZ3tef*1LROe4eB0Pu)G` z1&Ximc^kW5^C~|cb$>mv0-7(sM##T>5Idi0d>yO-U9ab9tiWKg@4fqq88SbLlJG-c zt*91Qtip#RF)mOPIZO~Za0El*FzjWLku4H0ll6Rd2-0VOkGqnaALUqH_kV^j_GAG+ z6FL?Q>9`-rc#S<9GqgB(Iu7j0rO)donbWt8B7vT)B9V0G(ffyRK3fhn&+dtrL)O*n z?I^%mw~rTl{o64?k9}{!PBC>hHF_{vGgWFuCQA$A^~}-m^N~3O?&>ts)D5bFj0_Z= z6cYnY?H#VNev{qu9o4oy-|ybmXl>PZyjd>S&HA-xY0Vi;Vn?AoyaR?qc`biBU=nSN zmnRi%)3jLa!VZLOlF?#ZJdm=of2@ThJRLtEp-%X>ku{oKjpu68-MS5$)gx+l9(>D^pn|EMGQRU*ZxeO4EETUBWz zZ&m^$kuocZ{zjPiaCbWVf|TB$+WLi_*Ve-}5>Lg`o-b2qSzp#$C0qJ=d-4c%!y3F- zX;d6J^{NrF5`*M;e5soq_eo{7S<2_RX=6&Z{r5vO{_L7IJu0HQg>Mu5q5Lg4K+$!; zZ-&_iArvA$BkrNFG?x2b!$LX^gYeElSZ-B~U8*JcEmoVn^fj7y(i%(L$g;o;qq|qr zNv}=8tlJ(5fNzs=`eiM7TZp(cNI;6z(Z(RU>GV=BZiGDwhlN(jwG$>9F&n0th0f_H z_2wx#{5dbjcGYO*T;>h!(MC56@FKbEE$FQKx$ceV^-+*7pbIrgLYPW16D|-`IX^A zIY9s8(@?=yBDv{g$*D+<%51c^<_f`BQX*|QN|?`>x>CJ^UBVg@5{f$-Uv?6CWvu2K z1i@Pvn|TAc-j>_t#5hNOe6#mm(TCulr?)|*%GpA~inXvtxD32FX|>&RWGN|8Ip%s2 z**Fq9ij^rsP+TP(-Z~8?LLiBW0jZ9&w(H$+_51CYo!C>>)-w=nPTj796wBQR==;s5fZZydqbib4v8sx1}A%~bz zfkip}2%P|#27J~@SK@h1MVLKd@^+m;TR$t<6fp{|st%G8InF{hGI{T$aHL7JzX=sNZeHsAMRNR# zv!F}wr@D>EQ?Rkm)h&NE?`{L<*A!|dn*T2$l3FQhmzLJ*R3jOqmL5=S<|3_1P5L-W zO{kC1*~k^cKT93NBE=vRBAB*NMxYaaylHupt0BU4 zLHxnUKpl~EYw7W$RqZ7Mlc3#X zf*sFc{H8`6-L0axaB`lb?@v4Bj)X(WvK%oRDn9mFk<{( zr0Se1i&491s<=jbY9GOB0nnYWS*9?I|2RPPi#PMSpeumu+fEtbHJVI z2wYvEJwqCsY0WOiIo*QV2;4ca(EU+~b&UAHh^oP_X6{;sxVRkf)OGX3S>W$`b7Y|+gFtZL;+HR;tdf&eK_!t zC0rK3;Y5+lk*srg6z0vfL#HgnrcAOek|3V@!|W!mz(0&$3iz{MI}tuEq1FLJ8u@3P zSiA~B9*6?3?jive5Vjf3A|BgpW@^1FBzhqwmG>$?jiE$1`D&f47mc!%sTLXDfOrBA zmEvtk46`mrITX45G2AsO=XC>8^fRJJeqkfr^mvm((DktPRG9h@$rgsh6i6W*h%4kK zW|^1aUoG~mZM^yoa&8Z(1jR9<18d>8*n}*{$|tzkw{f&;VklYK3u7t$GF|se=Gw`z zFy5FSP+CFm63Rrjsh1Gbn-9Kt>^bJgOY(%oiE(hdNtBaWOrgE&GPU5!-yYM0!9>Ku zAp(>?dXoL?m>}ycXa}E@It(xnG_cBytrkRCrE(I@>Ssb~mMjfwVCvgto{p;oS7I-Znog=T^X_6LrxWEQ zu`vte?-jKNZaOsmwg(A4!fql}a$>J9wXpd~G1HpK5x#NeX=Xmr@^kFb0CS(--$ULm zE!%$6&5{^vRv!bUa1l>vQZtAz;Tho)WbSHH6^S419-mIj68-5;QB<@7$}E%+7jZ*E z3rr19l=YBLu}2SzLG?33We;A0Bbq z$YS8EmtVNPh`xT|TO`_e2%vhasNF7{fmSA?(8_~M7Ezh9v@Hl60gZ^6I%1kQlk+5p zWZ|bDvKDe??LH`TQTUaPxu>lQaPsT6sx#d*(fqhdKd+ohz$J?{2s*Q23OCu^q`=si z-75R3BUX>)8x|g2T@mpTakZaa4?ey0MNnj(bgMKybUkun3$jh(m9#kf?xDY z_4YV_l{;@PH0*>!tH1;uw1*qO&qYkp#MBj$Vi@_?QR-q$yWy~iS~NMk@6eL@j!LJu zT;_PUYQ&)6&Pp9we|&E>VnNGOvqGrN1{+{C5IZ`7$sIs&9a&s5^iV!Zcp``q*Dl%? zgB#T>d^cN0D94{ZZGT{VVQPx>UKTv{=mm=a`@Av{%*qjSY9vh?9PWa$$qTs zNaKi1V&qK|&dgUl(>_s$*{Z& z+lE<~K6EM}pcnJI?U7WH^I<>MhW9ouFxMKeU=?d(sFA{=50S0xbqt+>&9Pp-WQmaU zRx%`GB$mi_^<09;$&X?gd^D;=e3blg`60>IBJ~tN-X_I%m>OG4){f4@6VN0Ew#z-o z2M&*@jHT;fOrwya3Gt)Sg)N(DdeR?leq7+NCPVeI`!vklR?KZsbsfN{lh^+&4IoQ< z2^$mgXYEZSJeq2_n7VN>(-)!0k2b%3>kcW4|pKm{AUk(gvV{&y@|AbD-fWo$d0upAg>31l8P z%`dSEDMmk<B+=Dx8Rs+h`mlHLVL0`!xdgA7PV0-ox?)@MyPlHsRy(?mO1g>#>)wwr$y(dGl5~R*H$O{W+12 zRd9<%1nUSbMo&P=?W7gF5KgQ7FuI5=N_bkj*kr${@&3K)a)4y_WhUMnutv`99#uE< zcdzO($srAy6Gjx(!Rb$9v`uQhfu0`iX_zzZh<^6tGVUR7Br8-Iy#~W~LGxwY`V&>v zaHT&yX?fUlMoX?kp+Fer z@*sdo?FAEcI^4)m$q5-FQQNe&~lw2D2g@nqx4IMhA5&1Xc*BOB4;N->J^2MLEr`3H$K%yD&`Dav<*}V zzcvtmf5oE%%k|wlk%0=4GY_gHH~jRidc^F0R-iHHr>1GAa2wP@y^-4B1{XFb+wfQx z$Xk$~G)u>wPsTB?i#1|b`jkGqhZ#&JB zJMMCLB|P`31@80H(+w~ zP`k7!oER{9qsSe<)b^JxpGUd68r~m$i<#T=> zPYN(y3x@)y##nKh_Pkq|%RodzA)WLzNi>8a?x{?NXe;^U8YY3vU;Jn!m61P?y!Qj+ z4hZFQDJw`K`hHbiK-wq4-33nI}0zy2X?Kr`wHNb9NA`x~uw_RiMQN>>SLd7yr5* zY$H2XB39J_8P6uu)n~CRpQff+0^e;K=_r$0^vNBC2hQ?U;h^gQZ^3hL4@{@c5L4)2nAUXzLuCX8@8r<9%oF?UrzWuA=QNd7FzxGZVQo1K zov;9kBWB!d)@)2m+4^sde_lLoc;7?-ZQ;}uEH&~C+-VDpZzuAMA5A?Sb42=g?n@7U z7>27ha2s*vy$$lW5uxVNV#Z*Mej`%g#nV=;!pP2TFlf$y9L0g)(3p&#@}Q8F#gFwz z)eWa_jLm@==4wt2d^Xv9Y3Wzk2)PXQ1cZtm+nh9NZcdW8i5vvBTSu}o<`c6xH=MI0 zI6gGMUe&WbeWyYefq|r1y{z7Lo$MF53$m z7Uo#fJ7nJo2V$~S)D4VL+`wW=G^AJtmMSv3X{8tuyGvwD7A@(131x?9glD?m>D%+A zBIURL2#VqNJpUbRxoFdEjs!y0HV<1^RWI3MnKVgfLx)&(0okn#)<8uWXmg$)0<-M` zXqEgmA=eB$+bJBxunvt8aD+ORQZDLu5LKK)k|+$egv&>LmQyE$zd;C+x3?Aq60GZJ zu`^QcFQRJws4xXnU;pCff{j*-!iO`BbgbV)N3b^Hy)j(6K^R30iek%q$}< zPDs*x1)E6%F@*OJ?muH2dMUWG}nD=1;6iFa{0dPD^LH{NY_Qs+YDB$y=0YWj7GB!5(fVZ8DuPkDR|6A;9IsuzO|sigeJ$%yo#W!(Jyx0gHNsKpz&b(U721t8&f6R`s}$28Akh;UQ>=CvF30 z508BQx!DKedapz6H-%CpV7j=-A)^N%*<%=4rm-V2+`5g*%u463)CMMJkb7Uo4l zR=4{?}XPir$NGd#u9u+hiO7#PFb8K#jN?F)9FRIn7{eLLegeFG9!LeendmF zyaTfBXA=WqGQ}@gDc9L^SX5<@Gl|bvHq}mcx|5V~n8+TqqQ_O(KEyktWS;En|A%51KarUGmrh-`wAtkZV-8_h5)x z!p*i@GEBZnfsR<=SN|?|g7kK9UDLdzwW@b0M`)dVgCgLtp$?R z`>{pZcH3qp09KGa%+8KDm+MLVF<9qOva`U;@`8O0w+ia_SxV}Jz zV$J#S{Ph~SV=Z>f{xS0RV8rBhJv5Oh1Tb9R1J-6_ZHvpaeAq>jRhik3^&3w6yFHB;&&b0LwF$01Td zfJ3IdH1|^_Sd*w}P((LJF=+h|bl^*~*Dta*+Na|*XkEQYaL$UfHoOa4VTa08b-(lJ zrFC#q7C&M!IBP||09)|y8HPFr^+b3jEnbj3+LAX2%aQWl*~q%dx#NUY={AH$_ZxS{ z0ISaF3Nr$FXKq#P(J0GgBU&AOWm;_lr_mrxmnn&uFb4cx3W3z$zKcM-*-q!*c40H1 zxXJ7;m;o(}JTw*+VG3K^`dc(VXy6bzc;5?fZQB?en`hV`L%?vhKU%(1)-|e4K-<6E z=k~Jzl!K-wxp|&i(l(T!B1znbHXuXYWdWJ|7idOkGHJY&>aaOn#}ns2L_pSz_*vjJ zoq92(XFqPEi|xhS9teS`xqCxdi^NhgXRA=dK{Wt|)|cH-C(rB@-bXs2jiE{bjU+wmTc8PId#a$g0El#$ax+ajCQ1*}+C zqXk=gMrmBJF~Y35ZY&X`{5<*2u@iFW-?*;h59_6O$NH~6pONh5Q=yLa=tY*J^=oo; zTQ$^bBIP7;EW4bhMWaWa3D@A2=-PrX7cZz|M#M{{SYtDk0Q#Sge`6}$ z?;=NsxP=bqQ*PPLGBlo{9RX3f>iTligXA!s7#R~98$oimbS@O;JMQgp2qHxyL3>BL zZ|&n>=c6KkYLr~jn&&Re_P$t>J3D@R0_A=?QOe;lM9gf10bQmI*(+-e{BRl_fMgxj zIOC++&`bzdd4ey}-(d1~+7lotb{spTy?w}eJTbBs!7>g$Z!5Z%J(9gKYtry2AXKS| z+&PVSekf6zTaGF>fW}z1@CZ#|DsWxxTRZvJTRkhGbq%dimZJb_18Em!%<4P_C)>4S z?}GK#%!O4siw%axGz);=o8+qaLE1Rm?Yn7SuD8laKsqtET6o<9MKo)pe(g@@RrY-r zO#vF@@p*^N{u0(&Te9YD%=f7}2lP=1JM0p~(Zpb#<$=xjL%2`ju*(wYOa<2zqi3T( zW#cQ2m;Is51F|cHy;gzzAgOLJW01$me8k2Z(7o5LsDybY45K@anARB1qemQhZ`1s3 zvSP;-7j4c3MkddolSO1yqblFp3t+#qs)L;D$fZ=23(9IS_8ZawP z9P0bNDRY=5hR%KzK@(KK!-EdgBi3R_0x{b5@L46@fVeTXB}1B#D}eA$M4Sbt5P~+0 z&jJ?)nZfKKWc4-Q`~CUg=N-O`Sn^^HLNa#GI4a1T-}MaQ z75J5g47>`P5%)QXi~G8ukowK1lv|-@U*3EWPl^+;j3ptmSiE|z&Y3!+5 zd|r{n4>w_UR^f81$GN;CH~5mJ_Z7CXj^*i!lR;Rdg(qgR3VrbNvh5RWodyZ&xQki@ zs_k_32UM%71M#xSusStd_&15(%T*Nkuq?tdc}EgbKW4I&L&WJZn_%Z3wEo z3L#`gy0(R!O2FM08PlCKp_P7h@1rzjGFJUP#oDI?Da71#f~G1WoJV)1sTqdRF-)Y- z%I{QJTzAD75+h(R2t_7M&YZ${!Knw#H|6HZyh$M-)BuBbZfE7ocA{>pw0~(8E4P2o zKRk8BCu{r^a=_$(#e?e`m};k%lnqVOP|u|My3j9R$v`trP&fd0N0;3^Kc_CA>ry`J z?Zg(N8w^nQ6t|zX6d=_LMTk$o;sOBQ_BAdmsLu8 z6U`mIEV^s%b_M1JH>*n`(rnf%d4ypiR-}{KZX`4hp$Rw*v(O%49XF4sEIxT*h@6sj z}}74WmWk1o|4S6U>0NHAl=W>UA=A;4WEWFswgq zx?b}JF`KB>{Ypdh@-vnpGjQU_fy`uyVcs(1b>(eYxr`a6j%g3gdgJ{2mAYQ?$&(bo zk#frR7Gx|D_ikstjP~lTDxMlVS7yPSo)eHGJ{S4)g@noQorPW+mV|ltBeQ~{P%x4r z-zX(x_7mA?Feevo7&eZ$MoK3vXRnXs25G+UXI~Lj7z?^|2+t9(zShuDQ@Wvo_F&LFAEqhAG)%1!MqA+IV=I%UCe78k&b ztS$Ru)>}J>;bE1Vr9@3G7gJv5FRK}y3pBE;H`OI;h3oa;p&{iy6e_=Ms60wwcriev)?`iU$!K73X1m+~T9KBP0we z9=~YX&RW&c%fDAS?d21#gk2ta0*1wp)vlETxyU{P?|Jtekz7Vwt-NGf(}D_?8et6G zJTn_x=c3BfQBQX_Q$geM2r3tc(9MI!yOjP$iB2DcKp2!ZD-A^sEM2>h`K~?6y$3ofFQk_%{cUzxLZ3kK_`oxg9MJ< zR$jd|AkF?#o_JMDe{j&8Dw;Xh*QaB4UZFVsh|Ovk>toy4O*EW2y5p z;FMc(-oZ70lps-`&nF)l=q7&5vYfj7Q>Z!Wa>UDvL>fPR2asDnBRW&&Xr*XX(8mEc zr3?`ix2mk}Z-{B*2X`og!dk1p=@5?$d=fj(Sgc(rjf zxPzexc{L!;dzObch9*!Cad_-@;`?{B<`s$Kc3cgV#`y1P1 zpH4~2^rEMGZKO22m5~WLJ*8nUz75-kwzxP+N>X zGyfR*BGDxX^hK0R`ui(49vrNxTp%AOGOKo?y0l%(%K@J0v3&T9Baaie5Yv5FsSVPd zrnbB{?xWOTZ?fwiT4bD~1VP;%nXQ}Q(+|)k$W^<=zf7+laVhlct+*-I)9LpmC`y}+ zjqoK6@sO~(`86nR0k|NyNvN!r(OUE&Il`eOWtjBg5u|YY?$l2xuX+6jn6cZe$M`Fk z^qG&n(=G4YZDY60w|nMQ4s8n@JM(}r*#=lC|GF4B7|JbD2YcXJYJ5=bT9Vxa@=s$s{Pj2m~eM`*h9( zoDoT4oT#C(sHHfPKo-|4w~JT!DEM{tx0K4EsVWe>4zITB)NbD|=f9P}CP_EFAVkY@ zx7TptK@rGnTQ%0J)m*9Xv?DlnW;RlIEuGGZD)UN7lM*q?>o7z*NK(N9ZHtiPaYAx3sWtgpl zm?R~KNsv{=0mKtd04zOU>+_5rz%sqOBI zRGY41ax=1l;3G+8m+j6`d8IS`v6Td1K~Z?jke1QtK-1;Sw{r?Wao7E}_H@_XcAtWe(FKUquEg)bUM0@*q~%*{FDL{ zcJ9$^2DR0eUPo3L*aG&JXMet90;)59y;EL|tLtRx#V-2gSd{tm9MPmI9p-SFv4>k% zdy0`t#UfcdsH~fyGGSpBWZ!kxuLnj{U%YO1vT8WC@HSyhCUsr@F|GSD0FC!x#xW>sTB#MY$DwHb1lmt1esSJtb1Rm0@f zz(lB|3&zWebG;*YDZ#ClYS-oX)Ubm<$}|bmZR853!P=S4CuaQ2Y8na?@$%^$Rov%W z-Fywv+B~~DBq__uh`SU#ZgvWGnWp5$U#E2)XAU7aXK!_K>^vk&7PzlO3K^prQzb|? z76z$Z&@?{c^4c8^KebJcOOJu#F@*w$Te_|z1WOiBhWWCUu~ zn;Moz&D9`yO_$vmroy`0CFQd)^Wqm#&PRambu!01Rmb_03nj+p&)kkR)U!1NGEiH< z8bTICUvBi@{r44?w+Ke0Hz#Tp&$6#j)T9Qmj8J9{kKtPqy{q!@>XuTp-}P<0HKH|dtusp7M zq}#*Ks%mM$s%?_DPdaCzGbkvZtmxU%0uG>sto})nY`ushh)IkYdXj&aY$pzE-R{tP z`eOF4y1&GQ7cgzDN?ffJA*@j$X}q}Zu!Z)C+#=1Od9^?7T(WXJXiP2X-18>Z$0ay+ z@Nd(Op3m}^&=b*rHeXj%z>kLhZMts!E7bp==4)G1fT4*Yz>vY}Zx^=dL@i4!MV3fz;)T^*Fk5(uy zjz|DhvaJQrU2`!s%F22VHmL=26->t#70Y6plEwjfWCzc&4E1FyCh1(n5-g0#xo5-=+;GP<3+-3Jn>7s!92Q77Rr#eoL_QDg!E!Tu*2a~De+6X$=okXLH8 z|NbtCQ=O@KzGyT+G6G^rSHpzrseKhn9yIm4KK2}~35f`$#I4D!sx|e}u_2s)hg+czKmhx5&iiv<}em$^w-H`TAM>awoktMV#ALe|G! zq*GyM4?gbCoquuco-04k z4_855YGQ3k5v*()x;1Gvo_&6}qO4Y$KJ0%=fSyhbMY;-9r*pr})Aq{^zaF7`YOu@c z6x@K56}s~Ms?11|*FtPub6ex~1g&I!9Ugk5BltP|cZ>t}Rvd z+Lb1tGXxp&aZrvu#cCADswJGmA}nQ`V7gc30sr75sms5Bs-#Ja;G?S;p;trGV$kqM zhi7j!w8wqrsr<5WZ*RUWz3FUzecsjGt910{p^>MH;ptc4c_Wj66j8(U7-y_~AhU=j zW5gI|FRgLIy$Dol;_ri-=RXo~xshXT8Xhok$V`xl$wUbUX?Ve3rbk;4lfm^O?&QDJ zy4>V@e-~iNl;qv|+xSmbqV??`K$*5&Kj#P0(F#&s&G^+2-`)|m!1Zb5Rwh)Hy!Y^5 z;;Y2I-0)r`a{4VSjpRK-nY6G|WaIN$fBnKBq=Lt-+z~&RkNxGKWX)(FYhPI(#;-v~ zFg8ZneE7=(*^uS75`9Tyst}QI8R7tq1>Miy5KvQ*;YAGsVN6bL`}0LeO_;c&r`GxI zw;uFQ9p2xjPDK@7rJ#1f%}d7>Uf@!+Ew>yNx^thgudc#e#i1F!J^+d_;+|IIPzEojVsQ5!r;_mbdihHw7y8j z#OY}rB#0AI;e4U81jt_hB6WI1D!vQYgSv-mtt;^&rkzPMK#-atCfY&nL$Upn$lg+} zE(2$mgSqUlA;bIvqiGT3Q)BY1q1KfvWZ2p)Gg^1+e;%Be0NJ?0{{<>Ue7WRE3w{K# zry$bg2hj?GHZfj&4Z?|VJPud&kM0Ye zLR?ZR*7$*~1-!Tkar8;IB##|rgB*_g7budU=+aUXjWBsrcYw5y#1Nsc#`(v!wgkM} z|5Xos;f-Qu)bG9Z2kF9&s1hVWI>$0q92u89Uep4IDN_6O8Sdlv`6s91BVR;J3RNfn z+djnIwBLE*@}@Nd>r1Eyi8L8*0F4pC*qEdsY7xTNOotb;!eHSIDo%OD`*xrrIwbk@ zwZc7%SX`|H?6|7yr@u|CN$eJY;VVB``~^x@JUn9k?~bZq`{R3Le=QgUbwYs6a=MUr zlO5aZ!)M?U<6EGoM4O!Db$oD6+0(D^uc9lv(|1N>@T38{ zvHt=kMM-W^<{P<01jbTFRHlz^$TU~3q)|KXo)s> zH9~i{)@(ikOCom#p%-MH^;NpQHGV4|dX5cYy-m#Lhh_3377COdqcoA-A^QAH!^TL2 zPtYW}&L;k%AW~E&AvK7VI zO0ke1lKlR@XMq4$4yS(?T4Dl{Ni2_agVyQBGn)8y1U9Uch4dGh$&9@}_yoko*Wb8| zcSZ?xItNBZfk|@B@)cag>C0Vc2%NZ`6k>muC`v_EY!jJoqQK z%p@0yG`Xy1c?F~&N0!Y`Q;nG-?bsgYpGB4pk%O<4jF$vVN{mC|_*DsiT4AcogWSmw+q(6MDXkTAG5P%hM`-%QInF!%$n8c(-@7ud!d*s4ulyo7h zXG`hmi+VxivLUw3QBygKL4(jpiV(o_9Ld?GW^#(Q@ng<1ATbv-iSbK>^pRDh5g;uh z*D}&o9uC=vAt^112)=BE^;d-Y5i>;=5Zn+Id8J4>V1C6eh)J@@-tby6|7DCa*5e)|pm#aG;*FS*}B7 zV%n|`PU2KKHfl5+`MrE0`YcZFwf#Pyv=z7O@p$~OwdiP+xohehK`(&sv*1`JT%dg2zCq@5Y@0HH*n3tE?jeX z6CF?$wdSN2Vh%7R*q+QY+@>8Y)&BWGujS6?kHYKZ5QO{i4qn3y6nSHPe4BmjBm8bk zI1qp3lYVj0OpB{K>?4fXzqZC*&cjEG$5Yw7;nWw z#IvEbgs_fR^a_JFOU&r_HHKPckla$R*}h0xhjP-)EgdF;D;^X|2@wT_k{%7AY@?mr z?$~-qpKw3&F0p=}WHMjgED&ApY3qFN0aQ7QG?%;)Z|HpWI5&KXC$tXE=CZu0zoe-4 z{%Au7SBATSY@V~UgYfjV*Pi-Vd5AastR8L6sI;rANP~OzoXwJ(Dla0Uh|!hb_Dk1xgS;v zJ3Bd+S2~$k-o=^rYqK>GXPWIp846^jpkM?~7)E{F()|FABsvVVaPg9O-K`Ts`BOWk z%qD9m%m&4jH+s)B#G>NK3ptq*wekdvCi(JBjqh5FGi4q8k3TcQjwffO#i5!zcwr%< z5W;tl2g;B4qlg^WYHlbOXBVcAXQ%|zw0L>>GtR!OkLp~P*Bds0z(Zv6Alx>VybV^trc zktct#0(vRcS)!hKYe z^iJ2t62`q#$`OW{XTnS0q$qchI1n}*+@{d!p>fO`-sq^0(CcLLk?v}E#M=Ue`96ur zHg`51!uto7CiBr`!bK)%9$TN3Ho_u38?ONLUmupsQ?EdJF~^lo+VJe&#mq$-wMdu% zM=y7>57oy7C=~v;`uLQLtB%uRr0$33^x~_`v%719&(0Ii9-ADkwAgB$9lgv@qU$We z-{$9>HqKVQucHcrug`CcpXFWj2p2vx^A%|Y!y2Yt?FSREN>t}D24U!Ln|Cvr)EV`Hk{!Jq){|d?fkWTy~ zDU$nFQWUh){zJzV6hhboj8m@%doEjHUL)eql9bey9Uc~gj{GKNeXob8y%zBlo*puu ztAKn&mLw5z&mZi|h9oAfyJhPF_>K&!-|!LzaH3Jcc_-076VHG3mja0(OWBhz8k5RS zONeNgj#s(odZ#|`KU&1kCgNl@h)lmg=Aix)L3LUW5#BctkRV795S)J^u&{In*gLuZ zJ0ohG@3cl2#)iChB!O9F6KQC`0{1?8BcNl$u8GYNflUO{yrQ_709M(!l0~QuL!f+t zk-(B_feY6#OVObRvEk12^-%<)ppe|aH8|eKv@0`sMy44>u9_S8@UdQfBQe=FX%&kY zZ+>%2lx)UI3ar+1`m7-LX5hgZAi1HKcPbPM0pcd+Q2ATWlprXQg(u6eKdVy-!<4c? zp5Sx932uyWp{;+Npq%&#$;fZ;=VD$`yolPx@*LOjS*>yzdk1=qBoemxd_z% zO2`Z|l6^NIb%c5mW|!#?iiM(oiQK>4t*5jWeR`zh*biiY9Um-ZDp}j`C@KL}jAKCr z$pHOO-ZBl{T5#(N9Xvgvd)9&btZUN3Q&c#rmbOs$p`iIvYek8GD+9%msVwE7!MkXl z1p=NVL%I$YJ8e%bLgXD8X0Z1dGI-!aedp+-&CW2j+K`$B9{Gue2{?aeTAY!_49PVK-W=#Z4lvb}!QRjKWb z0{+;#5e;Saz9`k~cJi>}yj(BWU*^oh4Hm&RONoOA{&Lvlo{y>I76;fX6Yq_Vqb>o{+CZw{T0&xX-ECTDTERGh>?SL+B;fI zL`ks!?Gz~?eQc=OO!T(j5!{l8g5i?yJ{jx#p(O3KW;R`IEZj2scpljFMMNSYgLkA~ zpflZkGxJrDC&V5~aztBME6#*-3kh3L_^&Dx<}zLKgD?t!#8FBce!WAE-_yO|m#=<@ z>MLb9%Tag7>`*ES{5ORE``>ije?qXeH<7monEqFt;J@|$>qqqs!X2Z3L9hNRWdEby ze`1djhV5fQ7rSCs7bBkV_?=)FnL#%6$E zgog-?Ro@-v{tnNT2fOiWy_cZ;vv;yJ zv@n>USeCB?lOi`g~v0pp&#SF89X8QX?n~H(`My~DVIh~CqSU2TPDI9RltKR=#OL5-YHR6Y`)}g2HZw3V_&xo@P^yh< zP-MmTQOF6HQLKq<8<&(rE6$HQKp2|Pnp_n9M07NQ9Q~vm-4A+tdidx+y2QViA-K+4 zwYfiLPFQdF?hqwN&zU*EU(qc5sACOnY|ISId-rvo;9-^g#{R5`0iVLfKSQ>E)xh< zsePRBafV+QV8FMSAAoqUL|hmx0?nPlWgV>a-B#ABIwop-hZtc`f`#q4*s(yMjasKm z@G!VTwyUURYW*6G>K~O!)<1LXEq|@|zm@>3zNFDpYH@O69&q+0BNZHnOV1t5z8t`C z{NwhA8=P(Uzr51DFn#(H)yhK?o~ZgxciML2+ASrIS<}rTcQ1SEeL1xv4`KkE)TRQ%djQMBPzUQB*nOfR!s zbKcB9ciwQA>Tl7w6K8eiw&Q6z*Og+cbTvNQw0P>IJ6Dr6WZN6txn-{tgF|-YJhl70 z^O*^Iul24As;^G8{^gs$OF93-#kSeA7aVHRVbFffm@H^)^q|(kUT*D_{&w*Nm!I>m ztFy1X-*K1w!Zp9zMHB4ux8L%*bN;BKmDamsd_1nqt^v#6{ykc@@Y%mjPdoeOeNnO% zn^Th~Ht+VfD7$;H5p9*X(>>--oOfkKe_L;p&VQy|7sJmxoImIIUT)vdKZmMXpDe$A z&f)thk@TBW?za?J`A3${c_P@~Tp4n<(nRu;!h@?e54#i2Y-aVolr`hs!Mi3i?<`uC zDJZy7$Vf%{rpJ<&4y8pQN3B*Swrq5(pRJdAa6Rq`s+r{)rk?Gkf|Bt@jyGQ8%qrWVuS+?NL6)y>3 zgx3HgTm^T8qomrcfv5cr8wdaw`yOwYHK}EiwvdoeleBots@}bsYpq_NDs%FlT7Ueu z+oOxhk5bN_%P6y{X4u|)tgzu1!>P#bCYFNx6U|@UeEsDE*DJBy1w0qrKJ`poz*bh1 z8TQ7;Y??wdU%CE^vw~MQyp23+;#<>xt;XltFa3AafG!6&^AQbvBFEFjx4|)OVVPsGM zlG;FwazYH2V_y&sftZFqa{)90YT63mVKVqkLrFpC#-dLlAdD^3Lo*g>LIK?r^qvF4 zl%rPIO~LCZ^kzKBILKi+?bc|M%H>^PBK-_f&r#Uzr21y&}tsI1bGwjhEhMeB Date: Sat, 25 Feb 2023 21:53:09 -0500 Subject: [PATCH 57/74] fixed premium check --- src/actions/tickets/delete.ts | 2 +- src/commands/nucleus/premium.ts | 67 +++++++++++++++++++++++++++++++-- src/premium/createTranscript.ts | 4 +- src/utils/database.ts | 8 ++-- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index a1f63d2..43e9dd2 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -9,7 +9,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI if (!interaction.guild) return; const config = await client.database.guilds.read(interaction.guild.id); const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; - + //FIXME const ticketChannel = config.tickets.category; if (!("parent" in interaction.channel!)) { return await interaction.reply({ diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 026984d..ed4f225 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,14 +1,73 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, StringSelectMenuBuilder } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; +import type { PremiumSchema } from "../../utils/database.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("premium").setDescription("Information about Nucleus Premium"); //TODO: Allow User to remove Premium + +const dmcallback = async (interaction: CommandInteraction, member: GuildMember, dbUser: PremiumSchema | null, firstDescription: string): Promise => { + + if(!dbUser) { + await interaction.editReply({embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription(`*You do not have premium! You can't activate premium on any servers.*` + firstDescription) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ]}); + return; + } + const premiumGuilds = dbUser.appliesTo.map((guildID) => { + const guild = client.guilds.cache.get(guildID); + if(!guild) return undefined; + return guild.name; + }); + + const options = premiumGuilds.filter((guild) => guild !== undefined) as string[]; + + const removeRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("currentPremium") + .setPlaceholder("Select a server to remove premium from") + .setDisabled(premiumGuilds.length === 0) + .addOptions(options.map((guild) => { + return {label: guild, value: guild} + })) + ); + const removeButton = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("removePremium") + .setLabel("Remove Premium") + .setStyle(ButtonStyle.Danger) + .setDisabled(premiumGuilds.length === 0) + ); + + await interaction.editReply( + { + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription(`*You have premium on the following servers:*` + firstDescription) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Success") + ], + components: [removeRow, removeButton] + }); + + //TODO Finish this. + + +} + const callback = async (interaction: CommandInteraction): Promise => { + await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => { interaction.editReply({ embeds: [ @@ -44,12 +103,14 @@ const callback = async (interaction: CommandInteraction): Promise => { premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server` count = 0; } + if(!interaction.guild) return await dmcallback(interaction, member, dbMember, firstDescription); const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); let premiumGuild = "" - if (hasPremium) { //FIXME: Check how user applied premium - premiumGuild = `**This server has premium! It was ${hasPremium[2] === 3 ? `automatically applied by <@${hasPremium[1]}>` : `given by <@${hasPremium[1]}>`}**\n\n` + if (hasPremium) { + premiumGuild = `**This server has premium! It was ${hasPremium[2] === 3 && hasPremium[3] ? `automatically applied by <@${hasPremium[1]}>` : `given by <@${hasPremium[1]}>`}**\n\n` } + const components: ActionRowBuilder[] = [] if (level === 0 || dbMember?.expiresAt) { components.push( diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 88a8e6e..acd641b 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -181,9 +181,11 @@ export default async function (interaction: CommandInteraction | MessageComponen if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; - + newOut.messages.push(msg); }); + console.log(newOut); + const topic = interaction.channel.topic; let member: GuildMember | null = null; if (topic !== null) { diff --git a/src/utils/database.ts b/src/utils/database.ts index e7336d0..48d0077 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -246,12 +246,12 @@ export class Premium { await this.premium.insertOne({ user: user, appliesTo: [], level: level }); } - async hasPremium(guild: string): Promise<[boolean, string, number] | null> { + async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> { const entries = await this.premium.find({}).toArray(); const members = await (await client.guilds.fetch(guild)).members.fetch() for(const {user} of entries) { const member = members.get(user); - if(member) { + if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod. const modPerms = //TODO: Create list in config for perms member.permissions.has("Administrator") || member.permissions.has("ManageChannels") || @@ -265,7 +265,7 @@ export class Premium { member.permissions.has("ManageMessages") || member.permissions.has("ManageThreads") const entry = entries.find(e => e.user === member.id); - if(entry && (entry.level === 3) && modPerms) return [true, member.id, entry.level]; + if(entry && (entry.level === 3) && modPerms) return [true, member.id, entry.level, true]; } } const entry = await this.premium.findOne({ @@ -275,7 +275,7 @@ export class Premium { } } }); - return entry ? [true, entry.user, entry.level] : null; + return entry ? [true, entry.user, entry.level, false] : null; } async fetchUser(user: string): Promise { From cfe8e9aebeceac4c6950be1f2098d95f7a9c64ca Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sun, 26 Feb 2023 17:28:09 -0500 Subject: [PATCH 58/74] yees Co-authored-by: PineappleFan Co-authored-by: Skyler --- src/actions/tickets/delete.ts | 4 +- src/commands/nucleus/premium.ts | 6 +-- src/premium/createTranscript.ts | 53 +++++++++++++++---- src/utils/client.ts | 6 ++- src/utils/database.ts | 92 ++++++++++++++++++++++++++++++++- 5 files changed, 142 insertions(+), 19 deletions(-) diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index 43e9dd2..a1b4b1a 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -9,7 +9,6 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI if (!interaction.guild) return; const config = await client.database.guilds.read(interaction.guild.id); const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; - //FIXME const ticketChannel = config.tickets.category; if (!("parent" in interaction.channel!)) { return await interaction.reply({ @@ -84,7 +83,8 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI embeds: [ new EmojiEmbed() .setTitle("Archived Ticket") - .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.` + + .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.\n` + + "Creating a transcript will delete all messages in this ticket" + await client.database.premium.hasPremium(interaction.guild.id) ? `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}` : "") diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index ed4f225..b31accb 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, StringSelectMenuBuilder } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, StringSelectMenuBuilder } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -10,7 +10,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("premium").setDescription("Information about Nucleus Premium"); //TODO: Allow User to remove Premium -const dmcallback = async (interaction: CommandInteraction, member: GuildMember, dbUser: PremiumSchema | null, firstDescription: string): Promise => { +const dmcallback = async (interaction: CommandInteraction, dbUser: PremiumSchema | null, firstDescription: string): Promise => { if(!dbUser) { await interaction.editReply({embeds: [ @@ -103,7 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise => { premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server` count = 0; } - if(!interaction.guild) return await dmcallback(interaction, member, dbMember, firstDescription); + if(!interaction.guild) return await dmcallback(interaction, dbMember, firstDescription); const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); let premiumGuild = "" if (hasPremium) { diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index acd641b..20b790a 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -8,7 +8,8 @@ import { TextChannel, ButtonStyle, User, - ComponentType + ComponentType, + ThreadChannel } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; @@ -78,14 +79,21 @@ interface Transcript { type: "ticket" | "purge" guild: string; channel: string; + for: TranscriptAuthor messages: TranscriptMessage[]; createdTimestamp: number; createdBy: TranscriptAuthor; } +const noTopic = new EmojiEmbed() + .setTitle("User not found") + .setDescription("There is no user associated with this ticket.") + .setStatus("Danger") + .setEmoji("CONTROL.BLOCKCROSS") + export default async function (interaction: CommandInteraction | MessageComponentInteraction) { if (interaction.channel === null) return; - if (!(interaction.channel instanceof TextChannel)) return; + if (!(interaction.channel instanceof TextChannel || interaction.channel instanceof ThreadChannel)) return; const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; let messages: Message[] = []; @@ -98,6 +106,13 @@ export default async function (interaction: CommandInteraction | MessageComponen messages = messages.concat(Array.from(deleted.values() as Iterable)); if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id) } while (deletedCount === 100); + messages = messages.filter(message => !( + message.components.some( + component => component.components.some( + child => child.customId?.includes("transcript") ?? false + ) + ) + )); let out = ""; messages.reverse().forEach((message) => { @@ -116,8 +131,30 @@ export default async function (interaction: CommandInteraction | MessageComponen const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) + let topic + let member: GuildMember | null = null; + if (interaction.channel instanceof TextChannel) { + topic = interaction.channel.topic; + if (topic === null) return await interaction.reply({ embeds: [noTopic] }); + member = interaction.guild!.members.cache.get(topic.split(" ")[1]!) ?? null; + } else { + topic = interaction.channel.name; + const split = topic.split("-").map(p => p.trim()) as [string, string, string]; + member = interaction.guild!.members.cache.get(split[1]) ?? null; + if (member === null) return await interaction.reply({ embeds: [noTopic] }); + } + + const newOut: Transcript = { type: "ticket", + for: { + username: member!.user.username, + discriminator: parseInt(member!.user.discriminator), + id: member!.user.id, + topRole: { + color: member!.roles.highest.color + } + }, guild: interaction.guild!.id, channel: interaction.channel!.id, messages: [], @@ -184,14 +221,8 @@ export default async function (interaction: CommandInteraction | MessageComponen newOut.messages.push(msg); }); - console.log(newOut); - - const topic = interaction.channel.topic; - let member: GuildMember | null = null; - if (topic !== null) { - const part = topic.split(" ")[0] ?? null; - if (part !== null) member = interaction.guild!.members.cache.get(part) ?? null; - } + const code = await client.database.transcripts.create(newOut); + if(!code) return await interaction.reply({embeds: [new EmojiEmbed().setTitle("Error").setDescription("An error occurred while creating the transcript.").setStatus("Danger").setEmoji("CONTROL.BLOCKCROSS")]}) let m: Message; if (out !== "") { const url = await pbClient.createPaste({ @@ -200,7 +231,7 @@ export default async function (interaction: CommandInteraction | MessageComponen name: `Ticket Transcript ${ member ? "for " + member.user.username + "#" + member.user.discriminator + " " : "" - }` + `(Created at ${new Date(interaction.channel.createdTimestamp).toDateString()})`, + }` + `(Created at ${new Date(interaction.channel.createdTimestamp!).toDateString()})`, publicity: Publicity.Unlisted }); const guildConfig = await client.database.guilds.read(interaction.guild!.id); diff --git a/src/utils/client.ts b/src/utils/client.ts index 857fb1d..00f8c05 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -2,7 +2,7 @@ import Discord, { Client, Interaction, AutocompleteInteraction, Collection } fro import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; -import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache } from "../utils/database.js"; +import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache, Transcript } from "../utils/database.js"; import EventScheduler from "../utils/eventScheduler.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js"; import config from "../config/main.js"; @@ -23,6 +23,7 @@ class NucleusClient extends Client { eventScheduler: EventScheduler; performanceTest: PerformanceTest; scanCache: ScanCache; + transcripts: Transcript }; preloadPage: Record = {}; // e.g. { channelID: { command: privacy, page: 3}} commands: Record; + + constructor() { + this.transcripts = database.collection("transcripts"); + } + + async create(transcript: Omit) { + let code; + do { + code = Math.random().toString(36).substring(2, 16) + Math.random().toString(36).substring(2, 16); + } while (await this.transcripts.findOne({ code: code })); + + const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code })); + if(doc.acknowledged) return code; + else return null; + } + + async read(code: string) { + return await this.transcripts.findOne({ code: code }); + } +} + export class History { histories: Collection; From 9c51a7ee53ba3a62c04cfe002f0b8c0681f09ffb Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Mon, 27 Feb 2023 17:11:13 -0500 Subject: [PATCH 59/74] made premium check faster. Added transcript endpoint, toHumanReadable function. --- package.json | 15 +- src/actions/tickets/delete.ts | 15 +- src/api/index.ts | 10 + src/commands/mod/purge.ts | 4 +- src/commands/nucleus/premium.ts | 4 +- src/commands/server/buttons.ts | 6 +- src/commands/settings/logs/attachment.ts | 1 + src/premium/attachmentLogs.ts | 1 + src/premium/createTranscript.ts | 271 ++++------------------ src/utils/client.ts | 2 +- src/utils/commandRegistration/register.ts | 4 +- src/utils/database.ts | 151 +++++++++++- src/utils/eventScheduler.ts | 1 - src/utils/logTranscripts.ts | 64 ----- 14 files changed, 223 insertions(+), 326 deletions(-) delete mode 100644 src/utils/logTranscripts.ts diff --git a/package.json b/package.json index 624e4b3..f28878f 100644 --- a/package.json +++ b/package.json @@ -3,30 +3,22 @@ "@discordjs/rest": "^0.2.0-canary.0", "@hokify/agenda": "^6.2.12", "@tsconfig/node18-strictest-esm": "^1.0.0", - "@types/node-cron": "^3.0.1", "@ungap/structured-clone": "^1.0.1", "agenda": "^4.3.0", - "ansi-styles": "^6.1.0", "body-parser": "^1.20.0", - "chalk": "^5.0.0", "discord.js": "^14.7.1", "eslint": "^8.21.0", "express": "^4.18.1", - "form-data": "^4.0.0", "fuse.js": "^6.6.2", "humanize-duration": "^3.27.1", "immutable": "^4.1.0", "lodash": "^4.17.21", "mongodb": "^4.7.0", - "node-cron": "^3.0.0", "node-fetch": "^3.3.0", "node-tesseract-ocr": "^2.2.1", - "pastebin-api": "^5.1.1", "structured-clone": "^0.2.2", - "systeminformation": "^5.17.3", - "typescript": "^4.9.4", - "uuid": "^8.3.2" - }, + "systeminformation": "^5.17.3" + }, "resolutions": { "discord-api-types": "0.37.23" }, @@ -68,6 +60,7 @@ "eslint-config-prettier": "^8.5.0", "prettier": "^2.7.1", "prettier-eslint": "^15.0.1", - "tsc-suppress": "^1.0.7" + "tsc-suppress": "^1.0.7", + "typescript": "^4.9.4" } } diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index a1b4b1a..be891ec 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -4,6 +4,7 @@ import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import { preloadPage } from '../../utils/createTemporaryStorage.js'; +import { LoadingEmbed } from '../../utils/defaults.js'; export default async function (interaction: Discord.CommandInteraction | ButtonInteraction) { if (!interaction.guild) return; @@ -68,8 +69,9 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI await channel.delete(); } else if (status === "Active") { - // Close the ticket - + await interaction.reply({embeds: LoadingEmbed, fetchReply: true}); + // Archive the ticket + await interaction.channel.fetch() if (channel.isThread()) { channel.setName(`${channel.name.replace("Active", "Archived")}`); channel.members.remove(channel.name.split(" - ")[1]!); @@ -79,13 +81,14 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI await channel.permissionOverwrites.delete(channel.topic!.split(" ")[0]!); } preloadPage(interaction.channel.id, "privacy", "2") - await interaction.reply({ + const hasPremium = await client.database.premium.hasPremium(interaction.guild.id); + await interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Archived Ticket") .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.\n` + "Creating a transcript will delete all messages in this ticket" + - await client.database.premium.hasPremium(interaction.guild.id) ? + hasPremium ? `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}` : "") .setStatus("Warning") @@ -100,7 +103,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI .setCustomId("closeticket") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) ].concat( - await client.database.premium.hasPremium(interaction.guild.id) + hasPremium ? [ new ButtonBuilder() .setLabel("Create Transcript and Delete") @@ -198,4 +201,4 @@ async function purgeByUser(member: string, guild: string) { log(data); } -export { purgeByUser }; \ No newline at end of file +export { purgeByUser }; diff --git a/src/api/index.ts b/src/api/index.ts index bfe9d18..6a90c48 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -149,6 +149,16 @@ const runServer = (client: NucleusClient) => { return res.sendStatus(404); }); + app.get("/transcript/:code", jsonParser, async function (req: express.Request, res: express.Response) { + const code = req.params.code; + if (code === undefined) return res.status(400).send("No code provided"); + const entry = await client.database.transcripts.read(code); + if (entry === null) return res.status(404).send("Could not find a transcript by that code"); + // Convert to a human readable format + const data = client.database.transcripts.toHumanReadable(entry); + return res.status(200).send(data); + }); + app.listen(port); }; diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 8d49c3b..5e0a795 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -1,4 +1,3 @@ -import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from '../../utils/logTranscripts.js'; import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -161,7 +160,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; log(data); - const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(deleted)!); + const newOut = await client.database.transcripts.createTranscript(deleted, interaction, interaction.member as GuildMember); + const transcript = client.database.transcripts.toHumanReadable(newOut); const attachmentObject = { attachment: Buffer.from(transcript), name: `purge-${channel.id}-${Date.now()}.txt`, diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index b31accb..325c360 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -67,7 +67,7 @@ const dmcallback = async (interaction: CommandInteraction, dbUser: PremiumSchema } const callback = async (interaction: CommandInteraction): Promise => { - + if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {}); await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => { interaction.editReply({ embeds: [ @@ -172,7 +172,7 @@ const callback = async (interaction: CommandInteraction): Promise => { components: [] }); } else { - client.database.premium.addPremium(interaction.user.id, guild.id); + await client.database.premium.addPremium(interaction.user.id, guild.id); interaction.editReply({ embeds: [ new EmojiEmbed() diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts index e4bda0d..3297616 100644 --- a/src/commands/server/buttons.ts +++ b/src/commands/server/buttons.ts @@ -32,7 +32,7 @@ const colors: Record = { const buttonNames: Record = { verifybutton: "Verify", rolemenu: "Role Menu", - createticket: "Ticket" + createticket: "Create Ticket" } export const callback = async (interaction: CommandInteraction): Promise => { @@ -195,8 +195,8 @@ export const callback = async (interaction: CommandInteraction): Promise = continue; } if (!out || out.isButton()) continue - data.title = out.fields.getTextInputValue("title"); - data.description = out.fields.getTextInputValue("description"); + data.title = out.fields.getTextInputValue("title").length === 0 ? null : out.fields.getTextInputValue("title"); + data.description = out.fields.getTextInputValue("description").length === 0 ? null : out.fields.getTextInputValue("description"); break; } case "send": { diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index c04c7cf..238b8b9 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -12,6 +12,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setDescription("Where attachments should be logged to (Premium only)") const callback = async (interaction: CommandInteraction): Promise => { + if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {}); await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 3c583f2..b2c8391 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -8,6 +8,7 @@ import addPlural from "../utils/plurals.js"; import type { GuildTextBasedChannel, Message } from "discord.js"; export default async function logAttachment(message: Message): Promise { + if (message.guild) client.database.premium.hasPremium(message.guild.id).finally(() => {}); if (!message.guild) throw new Error("Tried to log an attachment in a non-guild message"); const { renderUser, renderChannel, renderDelta } = client.logger; const attachments = []; diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 20b790a..85e059f 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -8,83 +8,13 @@ import { TextChannel, ButtonStyle, User, - ComponentType, ThreadChannel } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; -import { PasteClient, Publicity, ExpireDate } from "pastebin-api"; import client from "../utils/client.js"; import { messageException } from '../utils/createTemporaryStorage.js'; -const pbClient = new PasteClient(client.config.pastebinApiKey); - -interface TranscriptEmbed { - title?: string; - description?: string; - fields?: { - name: string; - value: string; - inline: boolean; - }[]; - footer?: { - text: string; - iconURL?: string; - }; -} - -interface TranscriptComponent { - type: number; - style?: ButtonStyle; - label?: string; - description?: string; - placeholder?: string; - emojiURL?: string; -} - -interface TranscriptAuthor { - username: string; - discriminator: number; - nickname?: string; - id: string; - iconURL?: string; - topRole: { - color: number; - badgeURL?: string; - } -} - -interface TranscriptAttachment { - url: string; - filename: string; - size: number; - log?: string; -} - -interface TranscriptMessage { - id: string; - author: TranscriptAuthor; - content?: string; - embeds?: TranscriptEmbed[]; - components?: TranscriptComponent[][]; - editedTimestamp?: number; - createdTimestamp: number; - flags?: string[]; - attachments?: TranscriptAttachment[]; - stickerURLs?: string[]; - referencedMessage?: string | [string, string, string]; -} - -interface Transcript { - type: "ticket" | "purge" - guild: string; - channel: string; - for: TranscriptAuthor - messages: TranscriptMessage[]; - createdTimestamp: number; - createdBy: TranscriptAuthor; -} - const noTopic = new EmojiEmbed() .setTitle("User not found") .setDescription("There is no user associated with this ticket.") @@ -114,175 +44,58 @@ export default async function (interaction: CommandInteraction | MessageComponen ) )); - let out = ""; - messages.reverse().forEach((message) => { - if (!message.author.bot) { - const sentDate = new Date(message.createdTimestamp); - out += `${message.author.username}#${message.author.discriminator} (${ - message.author.id - }) [${sentDate.toUTCString()}]\n`; - const lines = message.content.split("\n"); - lines.forEach((line) => { - out += `> ${line}\n`; - }); - out += "\n\n"; - } - }); - - const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) - let topic - let member: GuildMember | null = null; + let member: GuildMember = interaction.guild?.members.me!; if (interaction.channel instanceof TextChannel) { topic = interaction.channel.topic; if (topic === null) return await interaction.reply({ embeds: [noTopic] }); - member = interaction.guild!.members.cache.get(topic.split(" ")[1]!) ?? null; + const mem = interaction.guild!.members.cache.get(topic.split(" ")[1]!); + if (mem) member = mem; } else { topic = interaction.channel.name; const split = topic.split("-").map(p => p.trim()) as [string, string, string]; - member = interaction.guild!.members.cache.get(split[1]) ?? null; - if (member === null) return await interaction.reply({ embeds: [noTopic] }); + const mem = interaction.guild!.members.cache.get(split[1]) + if (mem) member = mem; } - - const newOut: Transcript = { - type: "ticket", - for: { - username: member!.user.username, - discriminator: parseInt(member!.user.discriminator), - id: member!.user.id, - topRole: { - color: member!.roles.highest.color - } - }, - guild: interaction.guild!.id, - channel: interaction.channel!.id, - messages: [], - createdTimestamp: Date.now(), - createdBy: { - username: interaction.user.username, - discriminator: parseInt(interaction.user.discriminator), - id: interaction.user.id, - topRole: { - color: interactionMember?.roles.highest.color ?? 0x000000 - } - } - } - if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; - messages.reverse().forEach((message) => { - const msg: TranscriptMessage = { - id: message.id, - author: { - username: message.author.username, - discriminator: parseInt(message.author.discriminator), - id: message.author.id, - topRole: { - color: message.member!.roles.highest.color - } - }, - createdTimestamp: message.createdTimestamp - }; - if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; - if (message.content) msg.content = message.content; - if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { - const obj: TranscriptEmbed = {}; - if (embed.title) obj.title = embed.title; - if (embed.description) obj.description = embed.description; - if (embed.fields.length > 0) obj.fields = embed.fields.map(field => { - return { - name: field.name, - value: field.value, - inline: field.inline ?? false - } - }); - if (embed.footer) obj.footer = { - text: embed.footer.text, - }; - if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL; - return obj; - }); - if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { - const obj: TranscriptComponent = { - type: child.type - } - if (child.type === ComponentType.Button) { - obj.style = child.style; - obj.label = child.label ?? ""; - } else if (child.type > 2) { - obj.placeholder = child.placeholder ?? ""; - } - return obj - })); - if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp; - msg.flags = message.flags.toArray(); - - if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); - if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; - newOut.messages.push(msg); - }); + const newOut = await client.database.transcripts.createTranscript(messages, interaction, member); const code = await client.database.transcripts.create(newOut); - if(!code) return await interaction.reply({embeds: [new EmojiEmbed().setTitle("Error").setDescription("An error occurred while creating the transcript.").setStatus("Danger").setEmoji("CONTROL.BLOCKCROSS")]}) - let m: Message; - if (out !== "") { - const url = await pbClient.createPaste({ - code: out, - expireDate: ExpireDate.Never, - name: - `Ticket Transcript ${ - member ? "for " + member.user.username + "#" + member.user.discriminator + " " : "" - }` + `(Created at ${new Date(interaction.channel.createdTimestamp!).toDateString()})`, - publicity: Publicity.Unlisted - }); - const guildConfig = await client.database.guilds.read(interaction.guild!.id); - m = (await interaction.reply({ - embeds: [ - new EmojiEmbed() - .setTitle("Transcript") - .setDescription( - "You can view the transcript using the link below. You can save the link for later" + - (guildConfig.logging.logs.channel - ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.` - : ".") - ) - .setStatus("Success") - .setEmoji("CONTROL.DOWNLOAD") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url), - new ButtonBuilder() - .setLabel("Delete") - .setStyle(ButtonStyle.Danger) - .setCustomId("close") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - ]) - ], - fetchReply: true - })) as Message; - } else { - m = (await interaction.reply({ - embeds: [ - new EmojiEmbed() - .setTitle("Transcript") - .setDescription( - "The transcript was empty, so no changes were made. To delete this ticket, press the delete button below." - ) - .setStatus("Success") - .setEmoji("CONTROL.DOWNLOAD") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Delete") - .setStyle(ButtonStyle.Danger) - .setCustomId("close") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - ]) - ], - fetchReply: true - })) as Message; - } + if(!code) return await interaction.reply({ + embeds: [ + new EmojiEmbed() + .setTitle("Error") + .setDescription("An error occurred while creating the transcript.") + .setStatus("Danger") + .setEmoji("CONTROL.BLOCKCROSS") + ] + }) + const guildConfig = await client.database.guilds.read(interaction.guild!.id); + const m: Message = (await interaction.reply({ + embeds: [ + new EmojiEmbed() + .setTitle("Transcript") + .setDescription( + "You can view the transcript using the link below. You can save the link for later" + + (guildConfig.logging.logs.channel + ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.` + : ".") + ) + .setStatus("Success") + .setEmoji("CONTROL.DOWNLOAD") + ], + components: [ + new ActionRowBuilder().addComponents([ + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), + new ButtonBuilder() + .setLabel("Delete") + .setStyle(ButtonStyle.Danger) + .setCustomId("close") + .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) + ]) + ], + fetchReply: true + })) as Message; let i; try { i = await m.awaitMessageComponent({ @@ -303,7 +116,7 @@ export default async function (interaction: CommandInteraction | MessageComponen timestamp: Date.now() }, list: { - ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"), + ticketFor: entry(member.id, renderUser(member.user)), deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)), deleted: entry(Date.now().toString(), renderDelta(Date.now())) }, diff --git a/src/utils/client.ts b/src/utils/client.ts index 00f8c05..7e84716 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -2,7 +2,7 @@ import Discord, { Client, Interaction, AutocompleteInteraction, Collection } fro import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; -import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache, Transcript } from "../utils/database.js"; +import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache, Transcript, } from "../utils/database.js"; import EventScheduler from "../utils/eventScheduler.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js"; import config from "../config/main.js"; diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 78e3b0f..d88a13a 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -225,6 +225,6 @@ export default async function register() { console.log(`${colors.green}Registered commands, events and context menus${colors.none}`) console.log( (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` : - `${colors.blue}Bot started in Production mode`) + colors.none) - // console.log(client.commands) + `${colors.blue}Bot started in Production mode`) + colors.none + ) }; diff --git a/src/utils/database.ts b/src/utils/database.ts index 7e80f96..b7971c3 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,4 +1,4 @@ -import type { ButtonStyle, GuildMember } from "discord.js"; +import { ButtonStyle, CommandInteraction, ComponentType, GuildMember, Message, MessageComponentInteraction } from "discord.js"; import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; import config from "../config/main.js"; @@ -154,7 +154,7 @@ interface TranscriptMessage { flags?: string[]; attachments?: TranscriptAttachment[]; stickerURLs?: string[]; - referencedMessage?: string | [string, string, string]; + referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id } interface TranscriptSchema { @@ -189,6 +189,134 @@ export class Transcript { async read(code: string) { return await this.transcripts.findOne({ code: code }); } + + async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) { + const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) + const newOut: Omit = { + type: "ticket", + for: { + username: member!.user.username, + discriminator: parseInt(member!.user.discriminator), + id: member!.user.id, + topRole: { + color: member!.roles.highest.color + } + }, + guild: interaction.guild!.id, + channel: interaction.channel!.id, + messages: [], + createdTimestamp: Date.now(), + createdBy: { + username: interaction.user.username, + discriminator: parseInt(interaction.user.discriminator), + id: interaction.user.id, + topRole: { + color: interactionMember?.roles.highest.color ?? 0x000000 + } + } + } + if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; + messages.reverse().forEach((message) => { + const msg: TranscriptMessage = { + id: message.id, + author: { + username: message.author.username, + discriminator: parseInt(message.author.discriminator), + id: message.author.id, + topRole: { + color: message.member!.roles.highest.color + } + }, + createdTimestamp: message.createdTimestamp + }; + if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; + if (message.content) msg.content = message.content; + if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { + const obj: TranscriptEmbed = {}; + if (embed.title) obj.title = embed.title; + if (embed.description) obj.description = embed.description; + if (embed.fields.length > 0) obj.fields = embed.fields.map(field => { + return { + name: field.name, + value: field.value, + inline: field.inline ?? false + } + }); + if (embed.footer) obj.footer = { + text: embed.footer.text, + }; + if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL; + return obj; + }); + if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { + const obj: TranscriptComponent = { + type: child.type + } + if (child.type === ComponentType.Button) { + obj.style = child.style; + obj.label = child.label ?? ""; + } else if (child.type > 2) { + obj.placeholder = child.placeholder ?? ""; + } + return obj + })); + if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp; + msg.flags = message.flags.toArray(); + + if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); + if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; + newOut.messages.push(msg); + }); + return newOut; + } + + toHumanReadable(transcript: Omit): string { + let out = ""; + for (const message of transcript.messages) { + if (message.referencedMessage) { + if (Array.isArray(message.referencedMessage)) { + out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`; + } + else out += `> [Reply To] ${message.referencedMessage}\n`; + } + out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id}) `; + out += `[${new Date(message.createdTimestamp).toISOString()}] `; + if (message.editedTimestamp) out += `[Edited: ${new Date(message.editedTimestamp).toISOString()}] `; + out += "\n"; + if (message.content) out += `[Content]\n${message.content}\n\n`; + if (message.embeds) { + for (const embed of message.embeds) { + out += `[Embed]\n`; + if (embed.title) out += `| Title: ${embed.title}\n`; + if (embed.description) out += `| Description: ${embed.description}\n`; + if (embed.fields) { + for (const field of embed.fields) { + out += `| Field: ${field.name} - ${field.value}\n`; + } + } + if (embed.footer) { + out += `|Footer: ${embed.footer.text}\n`; + } + out += "\n"; + } + } + if (message.components) { + for (const component of message.components) { + out += `[Component]\n`; + for (const button of component) { + out += `| Button: ${button.label ?? button.description}\n`; + } + out += "\n"; + } + } + if (message.attachments) { + for (const attachment of message.attachments) { + out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`; + } + } + } + return out + } } export class History { @@ -317,9 +445,12 @@ export class ModNotes { export class Premium { premium: Collection; + cache: Map; // Date indicates the time one hour after it was created + cacheTimeout = 1000 * 60 * 60; // 1 hour constructor() { this.premium = database.collection("premium"); + this.cache = new Map(); } async updateUser(user: string, level: number) { @@ -337,8 +468,11 @@ export class Premium { } async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> { + // [Has premium, user giving premium, level, is mod: if given automatically] + const cached = this.cache.get(guild); + if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]]; const entries = await this.premium.find({}).toArray(); - const members = await (await client.guilds.fetch(guild)).members.fetch() + const members = (await client.guilds.fetch(guild)).members.cache for(const {user} of entries) { const member = members.get(user); if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod. @@ -355,7 +489,10 @@ export class Premium { member.permissions.has("ManageMessages") || member.permissions.has("ManageThreads") const entry = entries.find(e => e.user === member.id); - if(entry && (entry.level === 3) && modPerms) return [true, member.id, entry.level, true]; + if(entry && (entry.level === 3) && modPerms) { + this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]); + return [true, member.id, entry.level, true]; + } } } const entry = await this.premium.findOne({ @@ -365,6 +502,7 @@ export class Premium { } } }); + this.cache.set(guild, [entry ? true : false, entry?.user ?? "", entry?.level ?? 0, false, new Date(Date.now() + this.cacheTimeout)]); return entry ? [true, entry.user, entry.level, false] : null; } @@ -427,11 +565,14 @@ export class Premium { } } - addPremium(user: string, guild: string) { + async addPremium(user: string, guild: string) { + const { level } = (await this.fetchUser(user))!; + this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true }); } removePremium(user: string, guild: string) { + this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } }); } } diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts index bdd0d21..5c461ad 100644 --- a/src/utils/eventScheduler.ts +++ b/src/utils/eventScheduler.ts @@ -14,7 +14,6 @@ class EventScheduler { collection: "eventScheduler" } }); - this.agenda.define("unmuteRole", async (job) => { const guild = await client.guilds.fetch(job.attrs.data.guild); const user = await guild.members.fetch(job.attrs.data.user); diff --git a/src/utils/logTranscripts.ts b/src/utils/logTranscripts.ts deleted file mode 100644 index 0950664..0000000 --- a/src/utils/logTranscripts.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type Discord from 'discord.js'; - -export interface JSONTranscriptSchema { - messages: { - content: string | null; - attachments: { - url: string; - name: string; - size: number; - }[]; - authorID: string; - authorUsername: string; - authorUsernameColor: string; - timestamp: string; - id: string; - edited: boolean; - }[]; - channel: string; - guild: string; - timestamp: string; -} - - -export const JSONTranscriptFromMessageArray = (messages: Discord.Message[]): JSONTranscriptSchema | null => { - if (messages.length === 0) return null; - return { - guild: messages[0]!.guild!.id, - channel: messages[0]!.channel.id, - timestamp: Date.now().toString(), - messages: messages.map((message: Discord.Message) => { - return { - content: message.content, - attachments: message.attachments.map((attachment: Discord.Attachment) => { - return { - url: attachment.url, - name: attachment.name!, - size: attachment.size, - }; - }), - authorID: message.author.id, - authorUsername: message.author.username + "#" + message.author.discriminator, - authorUsernameColor: message.member!.displayHexColor.toString(), - timestamp: message.createdTimestamp.toString(), - id: message.id, - edited: message.editedTimestamp ? true : false, - }; - }) - }; -} - -export const JSONTranscriptToHumanReadable = (data: JSONTranscriptSchema): string => { - let out = ""; - - for (const message of data.messages) { - const date = new Date(parseInt(message.timestamp)); - out += `${message.authorUsername} (${message.authorID}) [${date}]`; - if (message.edited) out += " (edited)"; - if (message.content) out += "\nContent:\n" + message.content.split("\n").map((line: string) => `\n> ${line}`).join(""); - if (message.attachments.length > 0) out += "\nAttachments:\n" + message.attachments.map((attachment: { url: string; name: string; size: number; }) => `\n> [${attachment.name}](${attachment.url}) (${attachment.size} bytes)`).join("\n"); - - out += "\n\n"; - } - return out; -} \ No newline at end of file From 8f5cb24dc2013157e411880612267dca69c8474f Mon Sep 17 00:00:00 2001 From: PineappleFan Date: Tue, 28 Feb 2023 16:40:19 +0000 Subject: [PATCH 60/74] Added raw and human readable api endpoints --- src/api/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/api/index.ts b/src/api/index.ts index 6a90c48..8c1e8cc 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -149,7 +149,7 @@ const runServer = (client: NucleusClient) => { return res.sendStatus(404); }); - app.get("/transcript/:code", jsonParser, async function (req: express.Request, res: express.Response) { + app.get("/transcript/:code/human", jsonParser, async function (req: express.Request, res: express.Response) { const code = req.params.code; if (code === undefined) return res.status(400).send("No code provided"); const entry = await client.database.transcripts.read(code); @@ -159,6 +159,15 @@ const runServer = (client: NucleusClient) => { return res.status(200).send(data); }); + app.get("/transcript/:code", jsonParser, async function (req: express.Request, res: express.Response) { + const code = req.params.code; + if (code === undefined) return res.status(400).send("No code provided"); + const entry = await client.database.transcripts.read(code); + if (entry === null) return res.status(404).send("Could not find a transcript by that code"); + // Convert to a human readable format + return res.status(200).send(entry); + }); + app.listen(port); }; From de618c4488b283dce32450e735a0ead8e4845b29 Mon Sep 17 00:00:00 2001 From: pineafan Date: Tue, 28 Feb 2023 18:03:18 +0000 Subject: [PATCH 61/74] Should work, don't have mongo configured --- src/commands/mod/purge.ts | 83 ++++++--------------------------- src/context/messages/purgeto.ts | 62 ++++++------------------ 2 files changed, 27 insertions(+), 118 deletions(-) diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 5e0a795..37b703e 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -1,4 +1,4 @@ -import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js"; +import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder, Message } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -309,35 +309,17 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; log(data); - let out = ""; // TODO: messageException throughout this file - messages.reverse().forEach((message) => { - if (!message) { - out += "Unknown message\n\n" - } else { - const author = message.author ?? { username: "Unknown", discriminator: "0000", id: "Unknown" }; - out += `${author.username}#${author.discriminator} (${author.id}) [${new Date( - message.createdTimestamp - ).toISOString()}]\n`; - if (message.content) { - const lines = message.content.split("\n"); - lines.forEach((line) => { - out += `> ${line}\n`; - }); - } - if (message.attachments.size > 0) { - message.attachments.forEach((attachment) => { - out += `Attachment > ${attachment.url}\n`; - }); - } - out += "\n\n"; - } - }); - const attachmentObject = { - attachment: Buffer.from(out), - name: `purge-${channel.id}-${Date.now()}.txt`, - description: "Purge log" - }; - const m = (await interaction.editReply({ + const messageArray: Message[] = messages.filter(message => !( + message!.components.some( + component => component.components.some( + child => child.customId?.includes("transcript") ?? false + ) + ) + )).map(message => message as Message); + const newOut = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember); + + const code = await client.database.transcripts.create(newOut); + await interaction.editReply({ embeds: [ new EmojiEmbed() .setEmoji("CHANNEL.PURGE.GREEN") @@ -347,47 +329,10 @@ const callback = async (interaction: CommandInteraction): Promise => { ], components: [ new Discord.ActionRowBuilder().addComponents([ - new Discord.ButtonBuilder() - .setCustomId("download") - .setLabel("Download transcript") - .setStyle(ButtonStyle.Success) - .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id")) + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), ]) ] - })) as Discord.Message; - let component; - try { - component = await m.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, - time: 300000 - }); - } catch { - return; - } - if (component.customId === "download") { - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.GREEN") - .setTitle("Purge") - .setDescription("Transcript uploaded above") - .setStatus("Success") - ], - components: [], - files: [attachmentObject] - }); - } else { - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.GREEN") - .setTitle("Purge") - .setDescription("Messages cleared") - .setStatus("Success") - ], - components: [] - }); - } + }); } }; diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts index 02d5d2a..aef159b 100644 --- a/src/context/messages/purgeto.ts +++ b/src/context/messages/purgeto.ts @@ -1,10 +1,8 @@ import confirmationMessage from '../../utils/confirmationMessage.js'; import EmojiEmbed from '../../utils/generateEmojiEmbed.js'; import { LoadingEmbed } from '../../utils/defaults.js'; -import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildTextBasedChannel, Message, MessageContextMenuCommandInteraction } from "discord.js"; +import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildMember, GuildTextBasedChannel, Message, MessageContextMenuCommandInteraction } from "discord.js"; import client from "../../utils/client.js"; -import getEmojiByName from '../../utils/getEmojiByName.js'; -import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from "../../utils/logTranscripts.js"; import { messageException } from '../../utils/createTemporaryStorage.js'; const command = new ContextMenuCommandBuilder() @@ -187,13 +185,16 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { log(data); const messages: Message[] = deleted.map(m => m).filter(m => m instanceof Message).map(m => m as Message); if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id) - const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(messages)!); - const attachmentObject = { - attachment: Buffer.from(transcript), - name: `purge-${channel.id}-${Date.now()}.txt`, - description: "Purge log" - }; - const m = (await interaction.editReply({ + const messageArray: Message[] = messages.filter(message => !( + message!.components.some( + component => component.components.some( + child => child.customId?.includes("transcript") ?? false + ) + ) + )).map(message => message as Message); + const transcript = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember); + const code = await client.database.transcripts.create(transcript); + await interaction.editReply({ embeds: [ new EmojiEmbed() .setEmoji("CHANNEL.PURGE.GREEN") @@ -203,47 +204,10 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { ], components: [ new Discord.ActionRowBuilder().addComponents([ - new Discord.ButtonBuilder() - .setCustomId("download") - .setLabel("Download transcript") - .setStyle(ButtonStyle.Success) - .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id")) + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), ]) ] - })) as Discord.Message; - let component; - try { - component = await m.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id, - time: 300000 - }); - } catch { - return; - } - if (component.customId === "download") { - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.GREEN") - .setTitle("Purge") - .setDescription("Transcript uploaded above") - .setStatus("Success") - ], - components: [], - files: [attachmentObject] - }); - } else { - interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.GREEN") - .setTitle("Purge") - .setDescription("Messages cleared") - .setStatus("Success") - ], - components: [] - }); - } + }); } const check = async (_interaction: MessageContextMenuCommandInteraction) => { From 088b1b217a050f8e5e0530b021f11d30d94e3477 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Tue, 28 Feb 2023 17:31:11 -0500 Subject: [PATCH 62/74] made minor fixed --- src/commands/mod/purge.ts | 2 +- src/commands/nucleus/_meta.ts | 6 ++---- src/utils/commandRegistration/register.ts | 2 +- src/utils/database.ts | 21 ++++++++++++++++----- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 37b703e..8644e26 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -29,7 +29,7 @@ const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; const user = (interaction.options.getMember("user") as GuildMember | null); const channel = interaction.channel as GuildChannel; - if (channel.isTextBased()) { + if (!channel.isTextBased()) { return await interaction.reply({ embeds: [ new EmojiEmbed() diff --git a/src/commands/nucleus/_meta.ts b/src/commands/nucleus/_meta.ts index 521b338..bd7fd14 100644 --- a/src/commands/nucleus/_meta.ts +++ b/src/commands/nucleus/_meta.ts @@ -3,8 +3,6 @@ import { command } from "../../utils/commandRegistration/slashCommandBuilder.js" const name = "nucleus"; const description = "Commands relating to Nucleus itself"; -const subcommand = await command(name, description, `nucleus`) +const subcommand = await command(name, description, `nucleus`, undefined, undefined, undefined, undefined, true); -const allowedInDMs = true; - -export { name, description, subcommand as command, allowedInDMs }; +export { name, description, subcommand as command }; diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index d88a13a..33c88b0 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -181,7 +181,7 @@ async function registerCommandHandler() { } async function execute(check: Function | undefined, callback: Function | undefined, data: CommandInteraction) { - console.log(client.commands["contextCommands/user/User info"]) + // console.log(client.commands["contextCommands/user/User info"]) if (!callback) return; if (check) { let result; diff --git a/src/utils/database.ts b/src/utils/database.ts index b7971c3..88efc70 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -3,6 +3,7 @@ import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; import config from "../config/main.js"; import client from "../utils/client.js"; +import * as crypto from "crypto"; const mongoClient = new MongoClient(config.mongoUrl); await mongoClient.connect(); @@ -133,7 +134,8 @@ interface TranscriptAuthor { topRole: { color: number; badgeURL?: string; - } + }; + bot: boolean; } interface TranscriptAttachment { @@ -178,7 +180,7 @@ export class Transcript { async create(transcript: Omit) { let code; do { - code = Math.random().toString(36).substring(2, 16) + Math.random().toString(36).substring(2, 16); + code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-"); } while (await this.transcripts.findOne({ code: code })); const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code })); @@ -200,7 +202,9 @@ export class Transcript { id: member!.user.id, topRole: { color: member!.roles.highest.color - } + }, + iconURL: member!.user.displayAvatarURL({ forceStatic: true}), + bot: member!.user.bot }, guild: interaction.guild!.id, channel: interaction.channel!.id, @@ -212,9 +216,12 @@ export class Transcript { id: interaction.user.id, topRole: { color: interactionMember?.roles.highest.color ?? 0x000000 - } + }, + iconURL: interaction.user.displayAvatarURL({ forceStatic: true}), + bot: interaction.user.bot } } + if(member.nickname) newOut.for.nickname = member.nickname; if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; messages.reverse().forEach((message) => { const msg: TranscriptMessage = { @@ -225,10 +232,13 @@ export class Transcript { id: message.author.id, topRole: { color: message.member!.roles.highest.color - } + }, + iconURL: member!.user.displayAvatarURL({ forceStatic: true}), + bot: message.author.bot }, createdTimestamp: message.createdTimestamp }; + if(message.member?.nickname) msg.author.nickname = message.member.nickname; if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; if (message.content) msg.content = message.content; if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { @@ -314,6 +324,7 @@ export class Transcript { out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`; } } + out += "\n\n" } return out } From 429c2d8753537e926683081dbfc4d9a8f6463c0b Mon Sep 17 00:00:00 2001 From: PineappleFan Date: Wed, 1 Mar 2023 08:15:50 +0000 Subject: [PATCH 63/74] Fixed grammar in nick --- src/commands/mod/nick.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 97f783a..ae4f446 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -93,7 +93,7 @@ const callback = async (interaction: CommandInteraction): Promise => { interaction.guild!.name }.` + (interaction.options.get("name")?.value as string - ? ` it is now: ${interaction.options.get("name")?.value as string}` + ? `/nIt is now: ${interaction.options.get("name")?.value as string}` : "") + "\n\n" + (createAppealTicket From 8d577fa079c60f4095ae6e261e8e1b3db94b06b9 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 1 Mar 2023 13:06:40 -0500 Subject: [PATCH 64/74] made minor fixes --- src/commands/mod/nick.ts | 1 - src/commands/nucleus/premium.ts | 131 ++++++++++++++++++-------------- src/utils/database.ts | 2 +- 3 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index ae4f446..0bd7849 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -11,7 +11,6 @@ import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("nick") - // .setNameLocalizations({"ru": "name", "zh-CN": "nickname"}) .setDescription("Changes a users nickname") .addUserOption((option) => option.setName("user").setDescription("The user to change").setRequired(true)) .addStringOption((option) => diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 325c360..c431c8e 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,74 +1,94 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, StringSelectMenuBuilder } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, ComponentType, Message, StringSelectMenuBuilder, StringSelectMenuInteraction } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; -import type { PremiumSchema } from "../../utils/database.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("premium").setDescription("Information about Nucleus Premium"); //TODO: Allow User to remove Premium -const dmcallback = async (interaction: CommandInteraction, dbUser: PremiumSchema | null, firstDescription: string): Promise => { - - if(!dbUser) { - await interaction.editReply({embeds: [ - new EmojiEmbed() - .setTitle("Premium") - .setDescription(`*You do not have premium! You can't activate premium on any servers.*` + firstDescription) - .setEmoji("NUCLEUS.LOGO") - .setStatus("Danger") - ]}); - return; - } - const premiumGuilds = dbUser.appliesTo.map((guildID) => { - const guild = client.guilds.cache.get(guildID); - if(!guild) return undefined; - return guild.name; - }); +const dmcallback = async (interaction: CommandInteraction, firstDescription: string, msg: Message): Promise => { + let closed = false; + do { + const dbUser = await client.database.premium.fetchUser(interaction.user.id); + if(!dbUser) { + await interaction.editReply({embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription(`*You do not have premium! You can't activate premium on any servers.*` + firstDescription) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ]}); + return; + } + const premiumGuilds = dbUser.appliesTo.map((guildID) => { + const guild = client.guilds.cache.get(guildID); + if(!guild) return undefined; + return guild.name; + }); - const options = premiumGuilds.filter((guild) => guild !== undefined) as string[]; + const options = premiumGuilds.filter((guild) => guild !== undefined) as string[]; - const removeRow = new ActionRowBuilder() - .addComponents( - new StringSelectMenuBuilder() - .setCustomId("currentPremium") - .setPlaceholder("Select a server to remove premium from") - .setDisabled(premiumGuilds.length === 0) - .addOptions(options.map((guild) => { - return {label: guild, value: guild} - })) - ); - const removeButton = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId("removePremium") - .setLabel("Remove Premium") - .setStyle(ButtonStyle.Danger) - .setDisabled(premiumGuilds.length === 0) - ); - - await interaction.editReply( - { - embeds: [ - new EmojiEmbed() - .setTitle("Premium") - .setDescription(`*You have premium on the following servers:*` + firstDescription) - .setEmoji("NUCLEUS.LOGO") - .setStatus("Success") - ], - components: [removeRow, removeButton] - }); - - //TODO Finish this. + const removeRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("currentPremium") + .setPlaceholder("Select a server to remove premium from") + .setDisabled(premiumGuilds.length === 0) + .addOptions(options.slice(0, Math.min(options.length, 24)).map((guild) => { + return {label: guild, value: guild} + })) + ); + const cancel = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("cancel") + .setLabel("Close") + .setStyle(ButtonStyle.Danger) + ); + const components: ActionRowBuilder[] = [cancel]; + if(options.length > 0) components.unshift(removeRow); + await interaction.editReply( + { + embeds: [ + new EmojiEmbed() + .setTitle("Premium") + .setDescription( + `*You have premium on the following servers:*\n\n` + + (options.length > 0 ? options.join(', ') : `You have not activated premium in any guilds`) + + firstDescription) + .setEmoji("NUCLEUS.LOGO") + .setStatus("Success") + ], + components: components + }); + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + const filter = (i: StringSelectMenuInteraction | ButtonInteraction) => i.user.id === interaction.user.id; + i = await msg.awaitMessageComponent({time: 300000, filter}) + } catch (e) { + await interaction.deleteReply(); + closed = true; + break; + } + await i.deferUpdate(); + if(i.isButton()) { + closed = true; + } else { + const response = client.database.premium.removePremium(interaction.user.id, i.values[0]!); + console.log(response) + } + } while (!closed); + await interaction.deleteReply(); } const callback = async (interaction: CommandInteraction): Promise => { if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {}); - await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) + const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true}) const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => { interaction.editReply({ embeds: [ new EmojiEmbed() @@ -103,14 +123,13 @@ const callback = async (interaction: CommandInteraction): Promise => { premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server` count = 0; } - if(!interaction.guild) return await dmcallback(interaction, dbMember, firstDescription); + if(!interaction.guild) return await dmcallback(interaction, firstDescription, m); const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); let premiumGuild = "" if (hasPremium) { premiumGuild = `**This server has premium! It was ${hasPremium[2] === 3 && hasPremium[3] ? `automatically applied by <@${hasPremium[1]}>` : `given by <@${hasPremium[1]}>`}**\n\n` } - const components: ActionRowBuilder[] = [] if (level === 0 || dbMember?.expiresAt) { components.push( @@ -144,7 +163,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") - // .setImage("") //TODO: Add image + .setImage("https://assets.clicks.codes/ads/ads/nucleus-premium.png") ], components: components }); diff --git a/src/utils/database.ts b/src/utils/database.ts index 88efc70..0688888 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -7,7 +7,7 @@ import * as crypto from "crypto"; const mongoClient = new MongoClient(config.mongoUrl); await mongoClient.connect(); -const database = mongoClient.db("Nucleus"); +const database = mongoClient.db(); export class Guilds { guilds: Collection; From faae533c40462b64ff173816141f4916ebfd894b Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 1 Mar 2023 18:16:05 -0500 Subject: [PATCH 65/74] made minor fixes --- TODO | 1 - src/config/format.ts | 27 ++++++++++++--- src/config/main.d.ts | 7 +++- src/utils/database.ts | 66 +++++++++++++++++++++++++++++++++---- src/utils/eventScheduler.ts | 2 +- 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/TODO b/TODO index 3316dd2..91d025a 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,2 @@ Server rules verificationRequired on welcome -!!Premium diff --git a/src/config/format.ts b/src/config/format.ts index a80094e..e32bef6 100644 --- a/src/config/format.ts +++ b/src/config/format.ts @@ -2,7 +2,7 @@ import fs from "fs"; import * as readLine from "node:readline/promises"; -const defaultDict: Record = { +const defaultDict: Record> = { developmentToken: "Your development bot token (Used for testing in one server, rather than production)", developmentGuildID: "Your development guild ID", enableDevelopment: true, @@ -15,7 +15,17 @@ const defaultDict: Record = { userContextFolder: "Your built user context folder (usually dist/context/users)", verifySecret: "If using verify, enter a code here which matches the secret sent back by your website. You can use a random code if you do not have one already. (Optional)", - mongoUrl: "Your Mongo connection string, e.g. mongodb://127.0.0.1:27017", + mongoUsername: "Your Mongo username (optional)", + mongoPassword: "Your Mongo password (optional)", + mongoDatabase: "Your Mongo database name (optional, e.g. Nucleus)", + mongoHost: "Your Mongo host (optional, e.g. localhost:27017)", + mongoOptions: { + username: "", + password: "", + database: "", + host: "", + authSource: "", + }, baseUrl: "Your website where buttons such as Verify and Role menu will link to, e.g. https://example.com/", pastebinApiKey: "An API key for pastebin (optional)", pastebinUsername: "Your pastebin username (optional)", @@ -97,6 +107,9 @@ export default async function (walkthrough = false) { json[key] = toWrite; break; } + case "mongoOptions": { + break; + } default: { json[key] = await getInput(`\x1b[36m${key} \x1b[0m(\x1b[35m${defaultDict[key]}\x1b[0m) > `); } @@ -107,8 +120,7 @@ export default async function (walkthrough = false) { } } if (walkthrough && !(json["mongoUrl"] ?? false)) json["mongoUrl"] = "mongodb://127.0.0.1:27017"; - if (!((json["mongoUrl"] as string | undefined) ?? "").endsWith("/")) json["mongoUrl"] += "/"; - if (!((json["baseUrl"] as string | undefined) ?? "").endsWith("/")) json["baseUrl"] += "/"; + if (!((json["baseUrl"] as string | undefined) ?? "").endsWith("/")) (json["baseUrl"] as string) += "/"; let hosts; try { hosts = fs.readFileSync("/etc/hosts", "utf8").toString().split("\n"); @@ -125,6 +137,13 @@ export default async function (walkthrough = false) { } json["mongoUrl"] = (json["mongoUrl"]! as string).replace("localhost", localhost!); json["baseUrl"] = (json["baseUrl"]! as string).replace("localhost", localhost!); + json["mongoOptions"] = { + username: json["username"] as string, + password: json["password"] as string, + database: json["database"] as string, + host: json["host"] as string, + authSource: json["authSource"] as string, + }; fs.writeFileSync("./src/config/main.ts", "export default " + JSON.stringify(json, null, 4) + ";"); diff --git a/src/config/main.d.ts b/src/config/main.d.ts index be6a253..6549234 100644 --- a/src/config/main.d.ts +++ b/src/config/main.d.ts @@ -10,7 +10,12 @@ declare const config: { messageContextFolder: string, userContextFolder: string, verifySecret: string, - mongoUrl: string, + mongoOptions: { + username: string, + password: string, + database: string, + host: string, + }, baseUrl: string, pastebinApiKey: string, pastebinUsername: string, diff --git a/src/utils/database.ts b/src/utils/database.ts index 0688888..1e2d3ba 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -5,10 +5,22 @@ import config from "../config/main.js"; import client from "../utils/client.js"; import * as crypto from "crypto"; -const mongoClient = new MongoClient(config.mongoUrl); +// config.mongoOptions.host, { +// auth: { +// username: config.mongoOptions.username, +// password: config.mongoOptions.password +// }, +// authSource: config.mongoOptions.authSource +// } +// mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority +const username = encodeURIComponent(config.mongoOptions.username); +const password = encodeURIComponent(config.mongoOptions.password); +const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); await mongoClient.connect(); const database = mongoClient.db(); +const collectionOptions = { authdb: "admin" }; + export class Guilds { guilds: Collection; defaultData: GuildConfig | null; @@ -25,11 +37,13 @@ export class Guilds { } async read(guild: string): Promise { + console.log("Guild read") const entry = await this.guilds.findOne({ id: guild }); return Object.assign({}, this.defaultData, entry); } async write(guild: string, set: object | null, unset: string[] | string = []) { + console.log("Guild write") // eslint-disable-next-line @typescript-eslint/no-explicit-any const uo: Record = {}; if (!Array.isArray(unset)) unset = [unset]; @@ -44,6 +58,7 @@ export class Guilds { // eslint-disable-next-line @typescript-eslint/no-explicit-any async append(guild: string, key: string, value: any) { + console.log("Guild append") if (Array.isArray(value)) { await this.guilds.updateOne( { id: guild }, @@ -70,6 +85,7 @@ export class Guilds { value: any, innerKey?: string | null ) { + console.log("Guild remove") if (innerKey) { await this.guilds.updateOne( { id: guild }, @@ -98,6 +114,7 @@ export class Guilds { } async delete(guild: string) { + console.log("Guild delete") await this.guilds.deleteOne({ id: guild }); } } @@ -114,6 +131,13 @@ interface TranscriptEmbed { text: string; iconURL?: string; }; + color?: number; + timestamp?: string; + author?: { + name: string; + iconURL?: string; + url?: string; + }; } interface TranscriptComponent { @@ -178,17 +202,19 @@ export class Transcript { } async create(transcript: Omit) { + console.log("Transcript create") let code; do { code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-"); } while (await this.transcripts.findOne({ code: code })); - const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code })); + const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions); if(doc.acknowledged) return code; else return null; } async read(code: string) { + console.log("Transcript read") return await this.transcripts.findOne({ code: code }); } @@ -233,7 +259,7 @@ export class Transcript { topRole: { color: message.member!.roles.highest.color }, - iconURL: member!.user.displayAvatarURL({ forceStatic: true}), + iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}), bot: message.author.bot }, createdTimestamp: message.createdTimestamp @@ -252,10 +278,17 @@ export class Transcript { inline: field.inline ?? false } }); + if (embed.color) obj.color = embed.color; + if (embed.timestamp) obj.timestamp = embed.timestamp if (embed.footer) obj.footer = { text: embed.footer.text, }; if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL; + if (embed.author) obj.author = { + name: embed.author.name + }; + if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL; + if (embed.author?.url) obj.author!.url = embed.author.url; return obj; }); if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { @@ -347,6 +380,7 @@ export class History { after?: string | null, amount?: string | null ) { + console.log("History create"); await this.histories.insertOne({ type: type, guild: guild, @@ -357,10 +391,11 @@ export class History { before: before ?? null, after: after ?? null, amount: amount ?? null - }); + }, collectionOptions); } async read(guild: string, user: string, year: number) { + console.log("History read"); const entry = (await this.histories .find({ guild: guild, @@ -375,6 +410,7 @@ export class History { } async delete(guild: string) { + console.log("History delete"); await this.histories.deleteMany({ guild: guild }); } } @@ -394,14 +430,17 @@ export class ScanCache { } async read(hash: string) { + console.log("ScanCache read"); return await this.scanCache.findOne({ hash: hash }); } async write(hash: string, data: boolean, tags?: string[]) { - await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); + console.log("ScanCache write"); + await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions); } async cleanup() { + console.log("ScanCache cleanup"); await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} }); } } @@ -414,10 +453,12 @@ export class PerformanceTest { } async record(data: PerformanceDataSchema) { + console.log("PerformanceTest record"); data.timestamp = new Date(); - await this.performanceData.insertOne(data); + await this.performanceData.insertOne(data, collectionOptions); } async read() { + console.log("PerformanceTest read"); return await this.performanceData.find({}).toArray(); } } @@ -441,15 +482,18 @@ export class ModNotes { } async create(guild: string, user: string, note: string | null) { + console.log("ModNotes create"); await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true }); } async read(guild: string, user: string) { + console.log("ModNotes read"); const entry = await this.modNotes.findOne({ guild: guild, user: user }); return entry?.note ?? null; } async delete(guild: string) { + console.log("ModNotes delete"); await this.modNotes.deleteMany({ guild: guild }); } } @@ -465,20 +509,24 @@ export class Premium { } async updateUser(user: string, level: number) { + console.log("Premium updateUser"); if(!(await this.userExists(user))) await this.createUser(user, level); await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true }); } async userExists(user: string): Promise { + console.log("Premium userExists"); const entry = await this.premium.findOne({ user: user }); return entry ? true : false; } async createUser(user: string, level: number) { - await this.premium.insertOne({ user: user, appliesTo: [], level: level }); + console.log("Premium createUser"); + await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions); } async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> { + console.log("Premium hasPremium"); // [Has premium, user giving premium, level, is mod: if given automatically] const cached = this.cache.get(guild); if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]]; @@ -518,12 +566,14 @@ export class Premium { } async fetchUser(user: string): Promise { + console.log("Premium fetchUser"); const entry = await this.premium.findOne({ user: user }); if (!entry) return null; return entry; } async checkAllPremium(member?: GuildMember) { + console.log("Premium checkAllPremium"); const entries = await this.premium.find({}).toArray(); if(member) { const entry = entries.find(e => e.user === member.id); @@ -577,12 +627,14 @@ export class Premium { } async addPremium(user: string, guild: string) { + console.log("Premium addPremium"); const { level } = (await this.fetchUser(user))!; this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true }); } removePremium(user: string, guild: string) { + console.log("Premium removePremium"); this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } }); } diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts index 5c461ad..a79a260 100644 --- a/src/utils/eventScheduler.ts +++ b/src/utils/eventScheduler.ts @@ -10,7 +10,7 @@ class EventScheduler { constructor() { this.agenda = new Agenda({ db: { - address: config.mongoUrl + "Nucleus", + address: config.mongoOptions.host, collection: "eventScheduler" } }); From f8ef79403fc88221e186239f13e01d627849343c Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 3 Mar 2023 15:32:32 -0500 Subject: [PATCH 66/74] worked on transcripts --- src/actions/tickets/delete.ts | 6 +- src/api/index.ts | 24 ++++++ src/commands/settings/automod.ts | 12 +-- src/commands/settings/moderation.ts | 1 + src/config/default.json | 122 ---------------------------- src/events/messageCreate.ts | 2 +- src/premium/createTranscript.ts | 5 +- src/utils/client.ts | 2 +- src/utils/database.ts | 76 ++++++++--------- 9 files changed, 74 insertions(+), 176 deletions(-) delete mode 100644 src/config/default.json diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index be891ec..990b360 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -87,10 +87,8 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI new EmojiEmbed() .setTitle("Archived Ticket") .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.\n` + - "Creating a transcript will delete all messages in this ticket" + - hasPremium ? - `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}` : - "") + hasPremium ? ("Creating a transcript will delete all messages in this ticket" + + `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}`): "") .setStatus("Warning") .setEmoji("GUILD.TICKET.ARCHIVED") ], diff --git a/src/api/index.ts b/src/api/index.ts index 8c1e8cc..9676194 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -156,6 +156,8 @@ const runServer = (client: NucleusClient) => { if (entry === null) return res.status(404).send("Could not find a transcript by that code"); // Convert to a human readable format const data = client.database.transcripts.toHumanReadable(entry); + res.attachment(`${code}.txt`); + res.type("txt"); return res.status(200).send(data); }); @@ -168,6 +170,28 @@ const runServer = (client: NucleusClient) => { return res.status(200).send(entry); }); + app.get("/channels/:id", jsonParser, async function (req: express.Request, res: express.Response) { + const id = req.params.id; + if (id === undefined) return res.status(400).send("No id provided"); + const channel = await client.channels.fetch(id); + if (channel === null) return res.status(404).send("Could not find a channel by that id"); + if (channel.isDMBased()) return res.status(400).send("Cannot get a DM channel"); + return res.status(200).send(channel.name); + }); + + app.get("/users/:id", jsonParser, async function (req: express.Request, res: express.Response) { + const id = req.params.id; + if (id === undefined) return res.status(400).send("No id provided"); + let user; + try { + user = await client.users.fetch(id); + } catch (e) { + console.log(e) + return res.status(404).send("Could not find a user by that id"); + } + return res.status(200).send(user.username); + }); + app.listen(port); }; diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index 87b1844..09b8914 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -650,19 +650,19 @@ const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, c channels?: string[], allowed?: { roles: string[], - user: string[] + users: string[] } }): Promise<{ channels: string[], allowed: { roles: string[], - user: string[] + users: string[] } }> => { let closed = false; - if(!current) current = {channels: [], allowed: {roles: [], user: []}}; + if(!current) current = {channels: [], allowed: {roles: [], users: []}}; if(!current.channels) current.channels = []; - if(!current.allowed) current.allowed = {roles: [], user: []}; + if(!current.allowed) current.allowed = {roles: [], users: []}; const channelMenu = new ActionRowBuilder() .addComponents( @@ -773,7 +773,7 @@ const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, c case "allowed": { switch (i.values[0]) { case "users": { - current.allowed.user = await toSelectMenu(interaction, m, current.allowed.user, "member", "Mention Settings"); + current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings"); break; } case "roles": { @@ -792,7 +792,7 @@ const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, c channels: string[], allowed: { roles: string[], - user: string[] + users: string[] } }; diff --git a/src/commands/settings/moderation.ts b/src/commands/settings/moderation.ts index c7f0dd0..336e53a 100644 --- a/src/commands/settings/moderation.ts +++ b/src/commands/settings/moderation.ts @@ -21,6 +21,7 @@ const callback = async (interaction: CommandInteraction): Promise => { while (!timedOut) { const config = await client.database.guilds.read(interaction.guild!.id); const moderation = config.moderation; + console.log(moderation) await interaction.editReply({ embeds: [ new EmojiEmbed() diff --git a/src/config/default.json b/src/config/default.json deleted file mode 100644 index 704896f..0000000 --- a/src/config/default.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "id": "default", - "version": 1, - "singleEventNotifications": { - "statsChannelDeleted": false - }, - "filters": { - "images": { - "NSFW": false, - "size": false - }, - "malware": false, - "wordFilter": { - "enabled": false, - "words": { - "strict": [], - "loose": [] - }, - "allowed": { - "user": [], - "roles": [], - "channels": [] - } - }, - "invite": { - "enabled": false, - "allowed": { - "user": [], - "roles": [], - "channels": [] - } - }, - "pings": { - "mass": 5, - "everyone": true, - "roles": true, - "allowed": { - "user": [], - "roles": [], - "channels": [], - "rolesToMention": [] - } - }, - "clean": { - "channels": [], - "allowed": { - "user": [], - "roles": [] - } - } - }, - "welcome": { - "enabled": false, - "role": null, - "ping": null, - "channel": null, - "message": null - }, - "stats": {}, - "logging": { - "logs": { - "enabled": true, - "channel": null, - "toLog": "3fffff" - }, - "staff": { - "channel": null - }, - "attachments": { - "channel": null, - "saved": {} - } - }, - "verify": { - "enabled": false, - "role": null - }, - "tickets": { - "enabled": false, - "category": null, - "types": "3f", - "customTypes": null, - "useCustom": false, - "supportRole": null, - "maxTickets": 5 - }, - "moderation": { - "mute": { - "timeout": true, - "role": null, - "text": null, - "link": null - }, - "kick": { - "text": null, - "link": null - }, - "ban": { - "text": null, - "link": null - }, - "softban": { - "text": null, - "link": null - }, - "warn": { - "text": null, - "link": null - }, - "nick": { - "text": null, - "link": null - } - }, - "tracks": [], - "roleMenu": [], - "tags": {}, - "autoPublish": { - "enabled": false, - "channels": [] - } -} diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 44017d7..4f525fc 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -28,7 +28,7 @@ export async function callback(_client: NucleusClient, message: Message) { if(config.filters.clean.channels.includes(message.channel.id)) { const memberRoles = message.member!.roles.cache.map(role => role.id); const roleAllow = config.filters.clean.allowed.roles.some(role => memberRoles.includes(role)); - const userAllow = config.filters.clean.allowed.user.includes(message.author.id); + const userAllow = config.filters.clean.allowed.users.includes(message.author.id); if(!roleAllow && !userAllow) return await message.delete(); } diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 85e059f..67aed04 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -86,7 +86,7 @@ export default async function (interaction: CommandInteraction | MessageComponen ], components: [ new ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript/${code}`), new ButtonBuilder() .setLabel("Delete") .setStyle(ButtonStyle.Danger) @@ -118,7 +118,8 @@ export default async function (interaction: CommandInteraction | MessageComponen list: { ticketFor: entry(member.id, renderUser(member.user)), deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)), - deleted: entry(Date.now().toString(), renderDelta(Date.now())) + deleted: entry(Date.now().toString(), renderDelta(Date.now())), + transcript: entry(code, `https://clicks.codes/nucleus/transcript/${code}`) }, hidden: { guild: interaction.guild!.id diff --git a/src/utils/client.ts b/src/utils/client.ts index 7e84716..b1fa31f 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -41,7 +41,7 @@ class NucleusClient extends Client { } } const client = new NucleusClient({ - guilds: await new Guilds().setup(), + guilds: await new Guilds(), history: new History(), notes: new ModNotes(), premium: new Premium(), diff --git a/src/utils/database.ts b/src/utils/database.ts index 1e2d3ba..2e64320 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -4,7 +4,8 @@ import { Collection, MongoClient } from "mongodb"; import config from "../config/main.js"; import client from "../utils/client.js"; import * as crypto from "crypto"; - +import _ from "lodash"; +import defaultData from '../config/default.js'; // config.mongoOptions.host, { // auth: { // username: config.mongoOptions.username, @@ -23,27 +24,22 @@ const collectionOptions = { authdb: "admin" }; export class Guilds { guilds: Collection; - defaultData: GuildConfig | null; + defaultData: GuildConfig; constructor() { this.guilds = database.collection("guilds"); - this.defaultData = null; - } - - async setup(): Promise { - this.defaultData = (await import("../config/default.json", { assert: { type: "json" } })) - .default as unknown as GuildConfig; - return this; + this.defaultData = defaultData; } async read(guild: string): Promise { - console.log("Guild read") + // console.log("Guild read") const entry = await this.guilds.findOne({ id: guild }); - return Object.assign({}, this.defaultData, entry); + const data = _.clone(this.defaultData!); + return _.merge(data, entry ?? {}); } async write(guild: string, set: object | null, unset: string[] | string = []) { - console.log("Guild write") + // console.log("Guild write") // eslint-disable-next-line @typescript-eslint/no-explicit-any const uo: Record = {}; if (!Array.isArray(unset)) unset = [unset]; @@ -58,7 +54,7 @@ export class Guilds { // eslint-disable-next-line @typescript-eslint/no-explicit-any async append(guild: string, key: string, value: any) { - console.log("Guild append") + // console.log("Guild append") if (Array.isArray(value)) { await this.guilds.updateOne( { id: guild }, @@ -85,7 +81,7 @@ export class Guilds { value: any, innerKey?: string | null ) { - console.log("Guild remove") + // console.log("Guild remove") if (innerKey) { await this.guilds.updateOne( { id: guild }, @@ -114,7 +110,7 @@ export class Guilds { } async delete(guild: string) { - console.log("Guild delete") + // console.log("Guild delete") await this.guilds.deleteOne({ id: guild }); } } @@ -202,7 +198,7 @@ export class Transcript { } async create(transcript: Omit) { - console.log("Transcript create") + // console.log("Transcript create") let code; do { code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-"); @@ -214,7 +210,7 @@ export class Transcript { } async read(code: string) { - console.log("Transcript read") + // console.log("Transcript read") return await this.transcripts.findOne({ code: code }); } @@ -322,9 +318,9 @@ export class Transcript { } else out += `> [Reply To] ${message.referencedMessage}\n`; } - out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id}) `; - out += `[${new Date(message.createdTimestamp).toISOString()}] `; - if (message.editedTimestamp) out += `[Edited: ${new Date(message.editedTimestamp).toISOString()}] `; + out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id})`; + out += ` [${new Date(message.createdTimestamp).toISOString()}]`; + if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`; out += "\n"; if (message.content) out += `[Content]\n${message.content}\n\n`; if (message.embeds) { @@ -380,7 +376,7 @@ export class History { after?: string | null, amount?: string | null ) { - console.log("History create"); + // console.log("History create"); await this.histories.insertOne({ type: type, guild: guild, @@ -395,7 +391,7 @@ export class History { } async read(guild: string, user: string, year: number) { - console.log("History read"); + // console.log("History read"); const entry = (await this.histories .find({ guild: guild, @@ -410,7 +406,7 @@ export class History { } async delete(guild: string) { - console.log("History delete"); + // console.log("History delete"); await this.histories.deleteMany({ guild: guild }); } } @@ -430,17 +426,17 @@ export class ScanCache { } async read(hash: string) { - console.log("ScanCache read"); + // console.log("ScanCache read"); return await this.scanCache.findOne({ hash: hash }); } async write(hash: string, data: boolean, tags?: string[]) { - console.log("ScanCache write"); + // console.log("ScanCache write"); await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions); } async cleanup() { - console.log("ScanCache cleanup"); + // console.log("ScanCache cleanup"); await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} }); } } @@ -453,12 +449,12 @@ export class PerformanceTest { } async record(data: PerformanceDataSchema) { - console.log("PerformanceTest record"); + // console.log("PerformanceTest record"); data.timestamp = new Date(); await this.performanceData.insertOne(data, collectionOptions); } async read() { - console.log("PerformanceTest read"); + // console.log("PerformanceTest read"); return await this.performanceData.find({}).toArray(); } } @@ -482,18 +478,18 @@ export class ModNotes { } async create(guild: string, user: string, note: string | null) { - console.log("ModNotes create"); + // console.log("ModNotes create"); await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true }); } async read(guild: string, user: string) { - console.log("ModNotes read"); + // console.log("ModNotes read"); const entry = await this.modNotes.findOne({ guild: guild, user: user }); return entry?.note ?? null; } async delete(guild: string) { - console.log("ModNotes delete"); + // console.log("ModNotes delete"); await this.modNotes.deleteMany({ guild: guild }); } } @@ -509,24 +505,24 @@ export class Premium { } async updateUser(user: string, level: number) { - console.log("Premium updateUser"); + // console.log("Premium updateUser"); if(!(await this.userExists(user))) await this.createUser(user, level); await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true }); } async userExists(user: string): Promise { - console.log("Premium userExists"); + // console.log("Premium userExists"); const entry = await this.premium.findOne({ user: user }); return entry ? true : false; } async createUser(user: string, level: number) { - console.log("Premium createUser"); + // console.log("Premium createUser"); await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions); } async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> { - console.log("Premium hasPremium"); + // console.log("Premium hasPremium"); // [Has premium, user giving premium, level, is mod: if given automatically] const cached = this.cache.get(guild); if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]]; @@ -566,14 +562,14 @@ export class Premium { } async fetchUser(user: string): Promise { - console.log("Premium fetchUser"); + // console.log("Premium fetchUser"); const entry = await this.premium.findOne({ user: user }); if (!entry) return null; return entry; } async checkAllPremium(member?: GuildMember) { - console.log("Premium checkAllPremium"); + // console.log("Premium checkAllPremium"); const entries = await this.premium.find({}).toArray(); if(member) { const entry = entries.find(e => e.user === member.id); @@ -627,14 +623,14 @@ export class Premium { } async addPremium(user: string, guild: string) { - console.log("Premium addPremium"); + // console.log("Premium addPremium"); const { level } = (await this.fetchUser(user))!; this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true }); } removePremium(user: string, guild: string) { - console.log("Premium removePremium"); + // console.log("Premium removePremium"); this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } }); } @@ -684,7 +680,7 @@ export interface GuildConfig { clean: { channels: string[]; allowed: { - user: string[]; + users: string[]; roles: string[]; } } From 75c51be81e8f30b598290bb0e7f630f08ab5d845 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 3 Mar 2023 17:18:18 -0500 Subject: [PATCH 67/74] worked on encryption --- .eslintignore | 2 +- src/api/index.ts | 4 ++-- src/commands/mod/purge.ts | 4 ++-- src/commands/privacy.ts | 7 ++++--- src/context/messages/purgeto.ts | 4 ++-- src/premium/createTranscript.ts | 4 ++-- src/utils/database.ts | 33 +++++++++++++++++++++++++++++---- 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/.eslintignore b/.eslintignore index 45ad95d..d7b2f7f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1 @@ -ClicksMigratingProblems/**/* +ClicksMigratingProblems/**/* \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts index 9676194..c8b7b14 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -152,7 +152,7 @@ const runServer = (client: NucleusClient) => { app.get("/transcript/:code/human", jsonParser, async function (req: express.Request, res: express.Response) { const code = req.params.code; if (code === undefined) return res.status(400).send("No code provided"); - const entry = await client.database.transcripts.read(code); + const entry = await client.database.transcripts.read(code, req.query.key as string, req.query.iv as string); if (entry === null) return res.status(404).send("Could not find a transcript by that code"); // Convert to a human readable format const data = client.database.transcripts.toHumanReadable(entry); @@ -164,7 +164,7 @@ const runServer = (client: NucleusClient) => { app.get("/transcript/:code", jsonParser, async function (req: express.Request, res: express.Response) { const code = req.params.code; if (code === undefined) return res.status(400).send("No code provided"); - const entry = await client.database.transcripts.read(code); + const entry = await client.database.transcripts.read(code, req.query.key as string, req.query.iv as string); if (entry === null) return res.status(404).send("Could not find a transcript by that code"); // Convert to a human readable format return res.status(200).send(entry); diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 8644e26..7728e56 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -318,7 +318,7 @@ const callback = async (interaction: CommandInteraction): Promise => { )).map(message => message as Message); const newOut = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember); - const code = await client.database.transcripts.create(newOut); + const [code, key, iv] = await client.database.transcripts.create(newOut); await interaction.editReply({ embeds: [ new EmojiEmbed() @@ -329,7 +329,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ], components: [ new Discord.ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript/${code}?key=${key}&iv=${iv}`).setDisabled(!code), ]) ] }); diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index 46784f5..69c8980 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -179,9 +179,10 @@ const callback = async (interaction: CommandInteraction): Promise => { continue; } if (confirmation.success) { - client.database.guilds.delete(interaction.guild!.id); - client.database.history.delete(interaction.guild!.id); - client.database.notes.delete(interaction.guild!.id); + await client.database.guilds.delete(interaction.guild!.id); + await client.database.history.delete(interaction.guild!.id); + await client.database.notes.delete(interaction.guild!.id); + await client.database.transcripts.deleteAll(interaction.guild!.id); nextFooter = "All data cleared"; continue; } else { diff --git a/src/context/messages/purgeto.ts b/src/context/messages/purgeto.ts index aef159b..0dc84e1 100644 --- a/src/context/messages/purgeto.ts +++ b/src/context/messages/purgeto.ts @@ -193,7 +193,7 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { ) )).map(message => message as Message); const transcript = await client.database.transcripts.createTranscript(messageArray, interaction, interaction.member as GuildMember); - const code = await client.database.transcripts.create(transcript); + const [code, key, iv] = await client.database.transcripts.create(transcript); await interaction.editReply({ embeds: [ new EmojiEmbed() @@ -204,7 +204,7 @@ const callback = async (interaction: MessageContextMenuCommandInteraction) => { ], components: [ new Discord.ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript/${code}?key=${key}&iv=${iv}`).setDisabled(!code), ]) ] }); diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 67aed04..fa5ec84 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -60,7 +60,7 @@ export default async function (interaction: CommandInteraction | MessageComponen const newOut = await client.database.transcripts.createTranscript(messages, interaction, member); - const code = await client.database.transcripts.create(newOut); + const [code, key, iv] = await client.database.transcripts.create(newOut); if(!code) return await interaction.reply({ embeds: [ new EmojiEmbed() @@ -86,7 +86,7 @@ export default async function (interaction: CommandInteraction | MessageComponen ], components: [ new ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript/${code}`), + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://testing.coded.codes/nucleus/transcript/${code}?key=${key}&iv=${iv}`), new ButtonBuilder() .setLabel("Delete") .setStyle(ButtonStyle.Danger) diff --git a/src/utils/database.ts b/src/utils/database.ts index 2e64320..0d21979 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -21,6 +21,7 @@ await mongoClient.connect(); const database = mongoClient.db(); const collectionOptions = { authdb: "admin" }; +const getIV = () => crypto.randomBytes(16); export class Guilds { guilds: Collection; @@ -203,15 +204,39 @@ export class Transcript { do { code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-"); } while (await this.transcripts.findOne({ code: code })); + const key = crypto.randomBytes(32**2).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-").substring(0, 32); + const iv = getIV().toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-"); + for(const message of transcript.messages) { + if(message.content) { + const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv); + message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64"); + } + } const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions); - if(doc.acknowledged) return code; - else return null; + if(doc.acknowledged) return [code, key, iv]; + else return [null, null, null]; } - async read(code: string) { + async read(code: string, key: string, iv: string) { // console.log("Transcript read") - return await this.transcripts.findOne({ code: code }); + const doc = await this.transcripts.findOne({ code: code }); + if(!doc) return null; + for(const message of doc.messages) { + if(message.content) { + const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv); + message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8"); + } + } + return doc; + } + + async deleteAll(guild: string) { + // console.log("Transcript delete") + const filteredDocs = await this.transcripts.find({ guild: guild }).toArray(); + for (const doc of filteredDocs) { + await this.transcripts.deleteOne({ code: doc.code }); + } } async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) { From cbe7f6bef62269d87822106a98590533e563dcd8 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Fri, 3 Mar 2023 17:46:08 -0500 Subject: [PATCH 68/74] added guildremove --- src/events/guildDelete.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/events/guildDelete.ts diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts new file mode 100644 index 0000000..92af401 --- /dev/null +++ b/src/events/guildDelete.ts @@ -0,0 +1,10 @@ +import client, { NucleusClient } from '../utils/client.js' +import type { Guild } from 'discord.js' + +export const event = 'guildDelete' +export const callback = async (_client: NucleusClient, guild: Guild) => { + await client.database.guilds.delete(guild.id); + await client.database.history.delete(guild.id); + await client.database.notes.delete(guild.id); + await client.database.transcripts.deleteAll(guild.id); +} \ No newline at end of file From f9baa95b10efc20b24d7f1877dd7e533c16d470d Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 4 Mar 2023 13:24:27 -0500 Subject: [PATCH 69/74] bleh commit --- src/utils/database.ts | 2 +- src/utils/log.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/database.ts b/src/utils/database.ts index 0d21979..b0c940e 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -16,7 +16,7 @@ import defaultData from '../config/default.js'; // mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority const username = encodeURIComponent(config.mongoOptions.username); const password = encodeURIComponent(config.mongoOptions.password); -const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); +const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanim=${config.mongoOptions.authMechanism || "DEFAULT"}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); await mongoClient.connect(); const database = mongoClient.db(); diff --git a/src/utils/log.ts b/src/utils/log.ts index c6416a1..11cabe8 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -79,7 +79,9 @@ export const Logger = { }, async log(log: LoggerOptions): Promise { if (!await isLogging(log.hidden.guild, log.meta.calculateType)) return; + console.log(log.hidden.guild) const config = await client.database.guilds.read(log.hidden.guild); + console.log(config.logging.logs.channel) if (config.logging.logs.channel) { const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null; const description: Record = {}; From 3ae91322669f94852a3a6509760068cef7446c85 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 4 Mar 2023 13:28:50 -0500 Subject: [PATCH 70/74] bleh commit --- src/config/main.d.ts | 4 +--- src/utils/database.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/config/main.d.ts b/src/config/main.d.ts index 6549234..75eb9e0 100644 --- a/src/config/main.d.ts +++ b/src/config/main.d.ts @@ -15,11 +15,9 @@ declare const config: { password: string, database: string, host: string, + authSource: string }, baseUrl: string, - pastebinApiKey: string, - pastebinUsername: string, - pastebinPassword: string, rapidApiKey: string }; diff --git a/src/utils/database.ts b/src/utils/database.ts index b0c940e..7230d07 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -16,7 +16,7 @@ import defaultData from '../config/default.js'; // mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority const username = encodeURIComponent(config.mongoOptions.username); const password = encodeURIComponent(config.mongoOptions.password); -const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanim=${config.mongoOptions.authMechanism || "DEFAULT"}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); +const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanim=DEFAULT` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); await mongoClient.connect(); const database = mongoClient.db(); From 7527657a829ceb4add5b6842daae2c87dd2c3f45 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 4 Mar 2023 13:49:16 -0500 Subject: [PATCH 71/74] nice commit --- src/utils/database.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/utils/database.ts b/src/utils/database.ts index 7230d07..99972b7 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -6,17 +6,10 @@ import client from "../utils/client.js"; import * as crypto from "crypto"; import _ from "lodash"; import defaultData from '../config/default.js'; -// config.mongoOptions.host, { -// auth: { -// username: config.mongoOptions.username, -// password: config.mongoOptions.password -// }, -// authSource: config.mongoOptions.authSource -// } -// mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority + const username = encodeURIComponent(config.mongoOptions.username); const password = encodeURIComponent(config.mongoOptions.password); -const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanim=DEFAULT` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); +const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); await mongoClient.connect(); const database = mongoClient.db(); From 78b90332ba08ea193fc84d7778457a43bb5d9fb5 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 4 Mar 2023 14:02:21 -0500 Subject: [PATCH 72/74] nice commit --- src/utils/database.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/database.ts b/src/utils/database.ts index 99972b7..45c7661 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -9,11 +9,11 @@ import defaultData from '../config/default.js'; const username = encodeURIComponent(config.mongoOptions.username); const password = encodeURIComponent(config.mongoOptions.password); -const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"}); +const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT` : `mongodb://${config.mongoOptions.host}`, {authSource: config.mongoOptions.authSource}); await mongoClient.connect(); const database = mongoClient.db(); -const collectionOptions = { authdb: "admin" }; +const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" }; const getIV = () => crypto.randomBytes(16); export class Guilds { From e7241a9a41c8b9fc4c2c8e0b8577081c0cba613b Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 4 Mar 2023 14:09:17 -0500 Subject: [PATCH 73/74] nice commit --- ClicksMigratingProblems/index.js | 139 ------- ClicksMigratingProblems/package-lock.json | 389 ------------------- ClicksMigratingProblems/package.json | 16 - ClicksMigratingProblems/randomPunishments.js | 30 -- 4 files changed, 574 deletions(-) delete mode 100644 ClicksMigratingProblems/index.js delete mode 100644 ClicksMigratingProblems/package-lock.json delete mode 100644 ClicksMigratingProblems/package.json delete mode 100644 ClicksMigratingProblems/randomPunishments.js diff --git a/ClicksMigratingProblems/index.js b/ClicksMigratingProblems/index.js deleted file mode 100644 index 34fc2c5..0000000 --- a/ClicksMigratingProblems/index.js +++ /dev/null @@ -1,139 +0,0 @@ -import fs from "fs"; -import { MongoClient } from "mongodb"; - -const mongoClient = new MongoClient("mongodb://127.0.0.1:27017/local"); -await mongoClient.connect(); -const database = mongoClient.db("Nucleus"); -const collection = database.collection("migrationTesting"); - -// Loop through all files in the oldData folder -const files = fs.readdirSync("./oldData"); -let x = 0; -for (const file of files) { - console.log(`┌ Processing file ${x} of ${files.length - 1} | ${file}`); - // Read the file as a json - let data; - try { - data = JSON.parse(fs.readFileSync(`./oldData/${file}`)); - } catch { - console.log(`└ Error reading file ${file}`); - x++; - continue; - } - // Check if data version is 3 - if (data.version !== 3) { - console.log(`├ Version was too old on ${file}`); - console.log("└ Skipping file"); - x++; - continue; - } - // Convert to the new format - const newData = { - id: data.guild_info.id.toString(), - version: 1, - singleEventNotifications: { - statsChannelDeleted: false - }, - filters: { - images: { - NSFW: !data.images.nsfw, - size: data.images.toosmall - }, - wordFilter: { - enabled: true, - words: { - strict: data.wordfilter.strict, - loose: data.wordfilter.soft - } - }, - invite: { - enabled: data.invite ? data.invite.enabled : false, - allowed: { - channels: data.invite ? data.invite.whitelist.channels.map((channel) => channel.toString()) : [], - users: [], - roles: [] - } - }, - pings: { - mass: 5, - everyone: true, - roles: true - } - }, - welcome: { - enabled: data.welcome ? data.welcome.message.text !== null : false, - verificationRequired: { - message: null, - role: null - }, - role: data.welcome ? (data.welcome.role !== null ? data.welcome.role.toString() : null) : null, - channel: data.welcome - ? data.welcome.message.text !== null - ? data.welcome.message.channel.toString() - : null - : null, - message: data.welcome ? data.welcome.message.text : null - }, - stats: {}, - logging: { - logs: { - enabled: true, - channel: data.log_info.log_channel ? data.log_info.log_channel.toString() : null, - toLog: "3fffff" - }, - staff: { - channel: data.log_info.staff ? data.log_info.staff.toString() : null - } - }, - verify: { - enabled: data.verify_role !== null, - role: data.verify_role ? data.verify_role.toString() : null - }, - tickets: { - enabled: data.modmail ? data.modmail.cat !== null : null, - category: data.modmail ? (data.modmail.cat !== null ? data.modmail.cat.toString() : null) : null, - types: "3f", - customTypes: null, - supportRole: data.modmail ? (data.modmail.mention !== null ? data.modmail.mention.toString() : null) : null, - maxTickets: data.modmail ? data.modmail.max : 5 - }, - moderation: { - mute: { - timeout: true, - role: null, - text: null, - link: null - }, - kick: { - text: null, - link: null - }, - ban: { - text: null, - link: null - }, - softban: { - text: null, - link: null - }, - warn: { - text: null, - link: null - }, - role: { - role: null - } - }, - tracks: [], - roleMenu: [], - tags: data.tags - }; - // Insert the new data into the database - await collection.updateOne({ id: data.guild_info.id.toString() }, { $set: newData }, { upsert: true }); - // Delete the old file - fs.unlinkSync(`./oldData/${file}`); - console.log(`└ Successfully migrated file ${file}`); - x++; -} - -// console.log((await collection.findOne({ id: "your mother" }))); diff --git a/ClicksMigratingProblems/package-lock.json b/ClicksMigratingProblems/package-lock.json deleted file mode 100644 index 5ec6ea0..0000000 --- a/ClicksMigratingProblems/package-lock.json +++ /dev/null @@ -1,389 +0,0 @@ -{ - "name": "hi", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "hi", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "fs": "^0.0.1-security", - "mongodb": "^4.7.0" - } - }, - "node_modules/@types/node": { - "version": "17.0.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz", - "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==" - }, - "node_modules/@types/webidl-conversions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", - "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" - }, - "node_modules/@types/whatwg-url": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", - "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bson": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz", - "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/denque": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", - "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "node_modules/mongodb": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.7.0.tgz", - "integrity": "sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA==", - "dependencies": { - "bson": "^4.6.3", - "denque": "^2.0.1", - "mongodb-connection-string-url": "^2.5.2", - "socks": "^2.6.2" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "saslprep": "^1.0.3" - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz", - "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", - "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", - "dependencies": { - "ip": "^1.1.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - } - }, - "dependencies": { - "@types/node": { - "version": "17.0.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz", - "integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==" - }, - "@types/webidl-conversions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", - "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" - }, - "@types/whatwg-url": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", - "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", - "requires": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bson": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz", - "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==", - "requires": { - "buffer": "^5.6.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "denque": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", - "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" - }, - "fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "mongodb": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.7.0.tgz", - "integrity": "sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA==", - "requires": { - "bson": "^4.6.3", - "denque": "^2.0.1", - "mongodb-connection-string-url": "^2.5.2", - "saslprep": "^1.0.3", - "socks": "^2.6.2" - } - }, - "mongodb-connection-string-url": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz", - "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==", - "requires": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", - "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.2.0" - } - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - } - } -} diff --git a/ClicksMigratingProblems/package.json b/ClicksMigratingProblems/package.json deleted file mode 100644 index d46e423..0000000 --- a/ClicksMigratingProblems/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "hi", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "fs": "^0.0.1-security", - "mongodb": "^4.7.0" - }, - "type": "module" -} diff --git a/ClicksMigratingProblems/randomPunishments.js b/ClicksMigratingProblems/randomPunishments.js deleted file mode 100644 index 432a9f8..0000000 --- a/ClicksMigratingProblems/randomPunishments.js +++ /dev/null @@ -1,30 +0,0 @@ -import { MongoClient } from "mongodb"; - -const mongoClient = new MongoClient("mongodb://127.0.0.1:27017/local"); -await mongoClient.connect(); -const database = mongoClient.db("Nucleus"); -const collection = database.collection("history"); - -for (let i = 0; i < 100; i++) { - // Select a type - let type = ["join", "unban", "leave", "ban", "softban", "kick", "mute", "purge", "warn", "nickname"][ - Math.floor(Math.random() * 9) - ]; - // Select a random date in the last year - let date = new Date(Date.now() - Math.floor(Math.random() * 31536000000)); - // Add to database - await collection.insertOne({ - type: type, - occurredAt: date, - user: "438733159748599813", - guild: "864185037078790195", - moderator: ["unban", "ban", "softban", "kick", "mute", "purge", "warn"].includes(type) - ? "438733159748599813" - : null, - reason: ["unban", "ban", "softban", "kick", "mute", "purge", "warn"].includes(type) ? "Test" : null, - before: type === "nickname" ? "TestBefore" : null, - after: type === "nickname" ? "TestAfter" : null, - amount: type === "purge" ? Math.floor(Math.random() * 100) : null - }); - console.log("Inserted document " + i); -} From 9f4cf9f444b5cc816e5192ee5e729296117220c0 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Sat, 4 Mar 2023 14:18:19 -0500 Subject: [PATCH 74/74] nice commit --- src/utils/database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/database.ts b/src/utils/database.ts index 49ed6f0..d000340 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -30,7 +30,7 @@ export class Guilds { async read(guild: string): Promise { // console.log("Guild read") const entry = await this.guilds.findOne({ id: guild }); - const data = _.clone(this.defaultData!); + const data = _.cloneDeep(this.defaultData); return _.merge(data, entry ?? {}); }