From 860b12d9d8bf483f0d54f2b3ac619a7d4fce9841 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Thu, 12 Oct 2023 21:14:05 +0530 Subject: [PATCH 01/13] feat(*): rewrite everything from scratch, use classes, decorators etc Still a work in progress --- api/.gitignore | 3 + api/index.ts | 42 + api/package-lock.json | 2638 +++++++++++++++++ api/package.json | 27 + api/tsconfig.json | 109 + archive/Levelling/Commands/Staff/level.ts | 97 - archive/Levelling/Commands/leaderboard.ts | 33 - archive/Levelling/Commands/rank.ts | 54 - archive/Levelling/Scripts/level/level.ts | 19 - archive/Levelling/Scripts/level/xp.ts | 38 - .../Levelling/Scripts/message/levelling.ts | 15 - archive/Levelling/index.ts | 4 - package-lock.json | 1902 ++++++++++-- package.json | 40 +- src/Commands/Apps/blacklist.ts | 140 - src/Commands/Apps/deleteMsg.ts | 57 - src/Commands/Apps/editMsg.ts | 162 - src/Commands/Apps/messageInfo.ts | 328 -- src/Commands/Apps/translate.ts | 124 - src/Commands/Developer/logout.ts | 14 - src/Commands/Information/help.ts | 291 -- src/Commands/Information/invite.ts | 34 - src/Commands/Information/listwarns.ts | 96 - src/Commands/Information/rules.ts | 11 - src/Commands/Information/stats.ts | 51 - src/Commands/Information/vote.ts | 34 - src/Commands/Main/hub.ts | 360 --- src/Commands/Main/network.ts | 58 - src/Commands/Network/blacklist.ts | 248 -- src/Commands/Staff/badge.ts | 79 - src/Commands/Staff/find.ts | 71 - src/Commands/Staff/purge.ts | 234 -- src/Commands/Staff/server.ts | 37 - src/Commands/Staff/suggestions.ts | 58 - src/Commands/Staff/warn.ts | 71 - src/Commands/Support/support.ts | 44 - src/Events/guildCreate.ts | 86 - src/Events/guildDelete.ts | 31 - src/Events/interactionCreate.ts | 73 - src/Events/messageCreate.ts | 176 -- src/Events/messageReactionAdd.ts | 61 - src/Events/ready.ts | 32 - src/Factory.ts | 13 + src/InterChat.ts | 53 + src/Scripts/badge/add.ts | 32 - src/Scripts/badge/list.ts | 25 - src/Scripts/badge/remove.ts | 29 - src/Scripts/blacklist/list.ts | 84 - src/Scripts/blacklist/server.ts | 81 - src/Scripts/blacklist/user.ts | 79 - src/Scripts/find/server.ts | 70 - src/Scripts/find/user.ts | 53 - src/Scripts/hub/browse.ts | 295 -- src/Scripts/hub/connections.ts | 66 - src/Scripts/hub/create.ts | 126 - src/Scripts/hub/delete.ts | 69 - src/Scripts/hub/invite.ts | 159 - src/Scripts/hub/join.ts | 120 - src/Scripts/hub/joined.ts | 63 - src/Scripts/hub/leave.ts | 57 - src/Scripts/hub/manage.ts | 313 -- src/Scripts/hub/moderator.ts | 133 - src/Scripts/hub/settings.ts | 105 - src/Scripts/message/addBadges.ts | 16 - src/Scripts/message/antispam.ts | 69 - src/Scripts/message/checks.ts | 87 - src/Scripts/message/cleanup.ts | 53 - .../message/messageContentModifiers.ts | 38 - src/Scripts/network/displaySettings.ts | 400 --- src/Scripts/network/onboarding.ts | 99 - src/Scripts/networkLogs/modActions.ts | 192 -- src/Scripts/networkLogs/msgDelete.ts | 41 - src/Scripts/networkLogs/msgUpdate.ts | 36 - src/Scripts/reactions/reactionButton.ts | 65 - src/Scripts/reactions/sortReactions.ts | 9 - src/Scripts/reactions/updateMessage.ts | 81 - src/Scripts/reactions/viewReactionsMenu.ts | 128 - src/Scripts/server/leave.ts | 39 - src/Scripts/suggestions/takedown.ts | 54 - src/Scripts/suggestions/update.ts | 35 - src/Scripts/support/report.ts | 221 -- src/Scripts/support/server.ts | 13 - src/Scripts/support/suggest.ts | 78 - src/Scripts/warn/add.ts | 52 - src/Scripts/warn/clear.ts | 21 - src/Scripts/warn/remove.ts | 29 - src/SuperClient.ts | 99 + src/Utils/JSON/README.md | 4 - src/Utils/JSON/badwords.json | 391 --- src/Utils/JSON/emoji.json | 95 - src/Utils/blacklist.ts | 198 -- src/Utils/deploy-commands.ts | 79 - src/Utils/errorHandler.ts | 25 - src/Utils/network.ts | 90 - src/Utils/timers.ts | 92 - src/Utils/translate.ts | 11 - src/Utils/utils.ts | 235 -- src/Utils/wordFilter.ts | 43 - src/commands/Command.ts | 62 + src/commands/slash/Information/stats.ts | 102 + src/commands/slash/Main/connection.ts | 307 ++ src/commands/slash/Main/hub.ts | 185 ++ src/decorators/Interaction.ts | 14 + src/index.ts | 136 +- src/scripts/network/buildEmbed.ts | 30 + src/scripts/network/components.ts | 33 + src/structures/BlacklistManager.ts | 200 ++ src/structures/CommandHandler.ts | 63 + src/structures/CustomID.ts | 51 + src/structures/NSFWDetection.ts | 29 + src/structures/NetworkManager.ts | 358 +++ src/structures/Scheduler.ts | 38 + src/typings/index.d.ts | 27 + src/updater/ReactionUpdater.ts | 151 + src/updater/StatsUpdater.ts | 15 + .../BitFields.ts} | 4 +- src/utils/Constants.ts | 102 + src/utils/Db.ts | 5 + src/utils/JSON/emojis.json | 91 + src/utils/JSON/profanity.json | 383 +++ src/{Utils/logger.ts => utils/Logger.ts} | 3 +- .../paginator.ts => utils/Pagination.ts} | 53 +- src/utils/Profanity.ts | 46 + src/utils/RegisterCommands.ts | 48 + src/utils/Utils.ts | 52 + tsconfig.json | 27 +- typings/discord.d.ts | 25 - 127 files changed, 7133 insertions(+), 8832 deletions(-) create mode 100644 api/.gitignore create mode 100644 api/index.ts create mode 100644 api/package-lock.json create mode 100644 api/package.json create mode 100644 api/tsconfig.json delete mode 100644 archive/Levelling/Commands/Staff/level.ts delete mode 100644 archive/Levelling/Commands/leaderboard.ts delete mode 100644 archive/Levelling/Commands/rank.ts delete mode 100644 archive/Levelling/Scripts/level/level.ts delete mode 100644 archive/Levelling/Scripts/level/xp.ts delete mode 100644 archive/Levelling/Scripts/message/levelling.ts delete mode 100644 archive/Levelling/index.ts delete mode 100644 src/Commands/Apps/blacklist.ts delete mode 100644 src/Commands/Apps/deleteMsg.ts delete mode 100644 src/Commands/Apps/editMsg.ts delete mode 100644 src/Commands/Apps/messageInfo.ts delete mode 100644 src/Commands/Apps/translate.ts delete mode 100644 src/Commands/Developer/logout.ts delete mode 100644 src/Commands/Information/help.ts delete mode 100644 src/Commands/Information/invite.ts delete mode 100644 src/Commands/Information/listwarns.ts delete mode 100644 src/Commands/Information/rules.ts delete mode 100644 src/Commands/Information/stats.ts delete mode 100644 src/Commands/Information/vote.ts delete mode 100644 src/Commands/Main/hub.ts delete mode 100644 src/Commands/Main/network.ts delete mode 100644 src/Commands/Network/blacklist.ts delete mode 100644 src/Commands/Staff/badge.ts delete mode 100644 src/Commands/Staff/find.ts delete mode 100644 src/Commands/Staff/purge.ts delete mode 100644 src/Commands/Staff/server.ts delete mode 100644 src/Commands/Staff/suggestions.ts delete mode 100644 src/Commands/Staff/warn.ts delete mode 100644 src/Commands/Support/support.ts delete mode 100644 src/Events/guildCreate.ts delete mode 100644 src/Events/guildDelete.ts delete mode 100644 src/Events/interactionCreate.ts delete mode 100644 src/Events/messageCreate.ts delete mode 100644 src/Events/messageReactionAdd.ts delete mode 100644 src/Events/ready.ts create mode 100644 src/Factory.ts create mode 100644 src/InterChat.ts delete mode 100644 src/Scripts/badge/add.ts delete mode 100644 src/Scripts/badge/list.ts delete mode 100644 src/Scripts/badge/remove.ts delete mode 100644 src/Scripts/blacklist/list.ts delete mode 100644 src/Scripts/blacklist/server.ts delete mode 100644 src/Scripts/blacklist/user.ts delete mode 100644 src/Scripts/find/server.ts delete mode 100644 src/Scripts/find/user.ts delete mode 100644 src/Scripts/hub/browse.ts delete mode 100644 src/Scripts/hub/connections.ts delete mode 100644 src/Scripts/hub/create.ts delete mode 100644 src/Scripts/hub/delete.ts delete mode 100644 src/Scripts/hub/invite.ts delete mode 100644 src/Scripts/hub/join.ts delete mode 100644 src/Scripts/hub/joined.ts delete mode 100644 src/Scripts/hub/leave.ts delete mode 100644 src/Scripts/hub/manage.ts delete mode 100644 src/Scripts/hub/moderator.ts delete mode 100644 src/Scripts/hub/settings.ts delete mode 100644 src/Scripts/message/addBadges.ts delete mode 100644 src/Scripts/message/antispam.ts delete mode 100644 src/Scripts/message/checks.ts delete mode 100644 src/Scripts/message/cleanup.ts delete mode 100644 src/Scripts/message/messageContentModifiers.ts delete mode 100644 src/Scripts/network/displaySettings.ts delete mode 100644 src/Scripts/network/onboarding.ts delete mode 100644 src/Scripts/networkLogs/modActions.ts delete mode 100644 src/Scripts/networkLogs/msgDelete.ts delete mode 100644 src/Scripts/networkLogs/msgUpdate.ts delete mode 100644 src/Scripts/reactions/reactionButton.ts delete mode 100644 src/Scripts/reactions/sortReactions.ts delete mode 100644 src/Scripts/reactions/updateMessage.ts delete mode 100644 src/Scripts/reactions/viewReactionsMenu.ts delete mode 100644 src/Scripts/server/leave.ts delete mode 100644 src/Scripts/suggestions/takedown.ts delete mode 100644 src/Scripts/suggestions/update.ts delete mode 100644 src/Scripts/support/report.ts delete mode 100644 src/Scripts/support/server.ts delete mode 100644 src/Scripts/support/suggest.ts delete mode 100644 src/Scripts/warn/add.ts delete mode 100644 src/Scripts/warn/clear.ts delete mode 100644 src/Scripts/warn/remove.ts create mode 100644 src/SuperClient.ts delete mode 100644 src/Utils/JSON/README.md delete mode 100644 src/Utils/JSON/badwords.json delete mode 100644 src/Utils/JSON/emoji.json delete mode 100644 src/Utils/blacklist.ts delete mode 100644 src/Utils/deploy-commands.ts delete mode 100644 src/Utils/errorHandler.ts delete mode 100644 src/Utils/network.ts delete mode 100644 src/Utils/timers.ts delete mode 100644 src/Utils/translate.ts delete mode 100644 src/Utils/utils.ts delete mode 100644 src/Utils/wordFilter.ts create mode 100644 src/commands/Command.ts create mode 100644 src/commands/slash/Information/stats.ts create mode 100644 src/commands/slash/Main/connection.ts create mode 100644 src/commands/slash/Main/hub.ts create mode 100644 src/decorators/Interaction.ts create mode 100644 src/scripts/network/buildEmbed.ts create mode 100644 src/scripts/network/components.ts create mode 100644 src/structures/BlacklistManager.ts create mode 100644 src/structures/CommandHandler.ts create mode 100644 src/structures/CustomID.ts create mode 100644 src/structures/NSFWDetection.ts create mode 100644 src/structures/NetworkManager.ts create mode 100644 src/structures/Scheduler.ts create mode 100644 src/typings/index.d.ts create mode 100644 src/updater/ReactionUpdater.ts create mode 100644 src/updater/StatsUpdater.ts rename src/{Utils/hubSettingsBitfield.ts => utils/BitFields.ts} (95%) create mode 100644 src/utils/Constants.ts create mode 100644 src/utils/Db.ts create mode 100644 src/utils/JSON/emojis.json create mode 100644 src/utils/JSON/profanity.json rename src/{Utils/logger.ts => utils/Logger.ts} (95%) rename src/{Utils/paginator.ts => utils/Pagination.ts} (58%) create mode 100644 src/utils/Profanity.ts create mode 100644 src/utils/RegisterCommands.ts create mode 100644 src/utils/Utils.ts delete mode 100644 typings/discord.d.ts diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 00000000..c7df64ac --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,3 @@ +node_modules + +dist/ \ No newline at end of file diff --git a/api/index.ts b/api/index.ts new file mode 100644 index 00000000..b84d8dae --- /dev/null +++ b/api/index.ts @@ -0,0 +1,42 @@ +import express from 'express'; +import { node } from '@tensorflow/tfjs-node'; +import { load, predictionType } from 'nsfwjs'; + +const model = await load(); +const app = express(); +const port = 3000; + +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +const analyzeImage = async (url: string): Promise => { + const imageBuffer = await (await fetch(url)).arrayBuffer(); + + const imageTensor = await node.decodeImage(Buffer.from(imageBuffer), 3) as any; + const predictions = await model.classify(imageTensor); + imageTensor.dispose(); + + return predictions; +}; + +app.get('/nsfw', async (req, res) => { + const url = req.query.url; + + if (!url || typeof url !== 'string') return res.status(400).json({ error: 'Missing url query parameter.' }); + + const regex = + /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.png)/; + if (!regex.test(url)) { + return res + .status(400) + .send({ error: 'Invalid url parameter. Must be a valid PNG, JPG or JPEG image URL.' }); + } + + const predictions = await analyzeImage(url); + if (!predictions) return res.status(500).json({ error: 'Something went wrong while analyzing the image.' }); + return res.status(200).json(predictions); +}); + +app.listen(port, () => { + console.log(`API listening on port ${port}`); +}); diff --git a/api/package-lock.json b/api/package-lock.json new file mode 100644 index 00000000..4ad9c66c --- /dev/null +++ b/api/package-lock.json @@ -0,0 +1,2638 @@ +{ + "name": "api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "api", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@tensorflow/tfjs-node": "^4.11.0", + "express": "^4.18.2", + "nsfwjs": "^2.4.2", + "tsc-watch": "^6.0.4" + }, + "devDependencies": { + "@types/express": "^4.17.19", + "@types/node": "^20.8.4", + "typescript": "^5.2.2" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", + "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/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==" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nsfw-filter/gif-frames": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@nsfw-filter/gif-frames/-/gif-frames-1.0.2.tgz", + "integrity": "sha512-XZrbJWEN8YfVla5i+PD4Wj51rRlJ8OgnXiPjjOt/OsrbsCR9GZRD4jr953oNWcwiRaoIcOCFWQNMQukO7Yb1dA==", + "dependencies": { + "@nsfw-filter/save-pixels": "^2.3.4", + "get-pixels-frame-info-update": "3.3.2", + "multi-integer-range": "3.0.0" + } + }, + "node_modules/@nsfw-filter/save-pixels": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@nsfw-filter/save-pixels/-/save-pixels-2.3.4.tgz", + "integrity": "sha512-dRZXwrXadMvxwJYKChrDBqC6GNvxVqlmdkyvZJO5DV65qyBsHZw8bPg9CnX7EgpxGl6+4ba/MAdHDLxs2XoD0Q==", + "dependencies": { + "gif-encoder": "0.4.1", + "ndarray": "1.0.18", + "ndarray-ops": "1.2.2", + "pngjs-nozlib": "1.0.0", + "through": "2.3.4" + } + }, + "node_modules/@tensorflow/tfjs": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-3.21.0.tgz", + "integrity": "sha512-khcARd3/872llL/oF4ouR40qlT71mylU66PGT8kHP/GJ5YKj44sv8lDRjU7lOVlJK7jsJFWEsNVHI3eMc/GWNQ==", + "peer": true, + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.21.0", + "@tensorflow/tfjs-backend-webgl": "3.21.0", + "@tensorflow/tfjs-converter": "3.21.0", + "@tensorflow/tfjs-core": "3.21.0", + "@tensorflow/tfjs-data": "3.21.0", + "@tensorflow/tfjs-layers": "3.21.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz", + "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==", + "peer": true, + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.21.0.tgz", + "integrity": "sha512-N4zitIAT9IX8B8oe489qM3f3VcESxGZIZvHmVP8varOQakTvTX859aaPo1s8hK1qCy4BjSGbweooZe4U8D4kTQ==", + "peer": true, + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.21.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@types/webgl2": "0.0.6", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.21.0.tgz", + "integrity": "sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==", + "peer": true, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.21.0.tgz", + "integrity": "sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==", + "peer": true, + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@webgpu/types": "0.1.16", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-3.21.0.tgz", + "integrity": "sha512-eFLfw2wIcFNxnP2Iv/SnVlihehzKMumk1b5Prcx1ixk/SbkCo5u0Lt7OVOWaEOKVqvB2sT+dJcTjAh6lrCC/QA==", + "peer": true, + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-3.21.0.tgz", + "integrity": "sha512-CMVXsraakXgnXEnqD9QbtResA7nvV7Jz20pGmjFIodcQkClgmFFhdCG5N+zlVRHEz7VKG2OyfhltZ0dBq/OAhA==", + "peer": true, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-node": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.11.0.tgz", + "integrity": "sha512-dKMabHsyXEjVM9hSPITa9s7+SA7mqIRCN3ITbOoiVQ4JVlpSg2sffORWOQaRbYISP7F+l6RFiw0EB7t5vCXPzg==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "1.0.9", + "@tensorflow/tfjs": "4.11.0", + "adm-zip": "^0.5.2", + "google-protobuf": "^3.9.2", + "https-proxy-agent": "^2.2.1", + "progress": "^2.0.0", + "rimraf": "^2.6.2", + "tar": "^4.4.6" + }, + "engines": { + "node": ">=8.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.11.0.tgz", + "integrity": "sha512-s6Vbz3IvMz2zNbH8/VptpRXzkwVjmuzT48esYLXJxMKtTcob4m5Srdxo7B+eJSDrWYkutXruiivaWmihFmu5rA==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.11.0", + "@tensorflow/tfjs-backend-webgl": "4.11.0", + "@tensorflow/tfjs-converter": "4.11.0", + "@tensorflow/tfjs-core": "4.11.0", + "@tensorflow/tfjs-data": "4.11.0", + "@tensorflow/tfjs-layers": "4.11.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.11.0.tgz", + "integrity": "sha512-2zmGX9MuR8AwscSGOybz4fBOFgQDnj+ZCWGkLxDzbKecy9GxuilukT46xB2zU0kSq7Mf3ncfE/9eUEy6a7ZDqQ==", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.11.0.tgz", + "integrity": "sha512-sM/B65u+1T3U+Ctiq1fn5j6VmiLEZW7BpuSa3ZXDXtIS07MoZ2FTuO8BMudxEY4xGpTyoOzqTOGT9BaGO3qrWg==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.11.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-converter": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.11.0.tgz", + "integrity": "sha512-j2JEVwkqh+pyin+sxUiNUG7QOIU2S0+5SzN8LFXHlR90/bPvC2qiaaSPYdGG/BYidFc27QCHD3obBXrb1EE/ow==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-core": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.11.0.tgz", + "integrity": "sha512-t0mCNIco8wg6aZdHWT1d6ZuKtbbdY5y871ELWLSUA1+grXDvvaroHYh5eeJexJYXeg+EQ0/hzB0G8nLsLjlyVQ==", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.30", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.1", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.1.tgz", + "integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==" + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-data": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.11.0.tgz", + "integrity": "sha512-8E6CVpd7kxRFtVL7kvz6WF5jH18pNN2wEcm2yA87xq37JwcRsIPTkrmfyqCHlJZmiWn3RQbP59Sl05gbBnFo5w==", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-layers": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.11.0.tgz", + "integrity": "sha512-ErVqwxjpu2YM3uJRj2o5GbBTYViUwnqOb0wKWuCVukVmGeWrUzf1X00Ky3dP4xfilfAvq+B26dg7QN4YNHeaKg==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@webgpu/types": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.30.tgz", + "integrity": "sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==" + }, + "node_modules/@tensorflow/tfjs-node/node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.19.tgz", + "integrity": "sha512-UtOfBtzN9OvpZPPbnnYunfjM7XCI4jyk1NvnFhTVz5krYAnW4o5DCoIekvms+8ApqhB4+9wSge1kBijdfTSmfg==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.37", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", + "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/mime": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", + "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "dependencies": { + "undici-types": "~5.25.1" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" + }, + "node_modules/@types/qs": { + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", + "dev": true + }, + "node_modules/@types/seedrandom": { + "version": "2.4.31", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.31.tgz", + "integrity": "sha512-O8t5IyMqJ5qSxOR/UJ4hWL64ix05ofO7FV9IgMwVtUvHu7EsI8YyMJOg7SAWrWhDqizj1oxNZAGgfkCrhk7GTQ==" + }, + "node_modules/@types/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/webgl-ext": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", + "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==", + "peer": true + }, + "node_modules/@types/webgl2": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", + "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==", + "peer": true + }, + "node_modules/@webgpu/types": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", + "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==", + "peer": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.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==", + "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": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "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==" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.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==" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "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==", + "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/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js": { + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz", + "integrity": "sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==", + "hasInstallScript": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "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==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cwise-compiler": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz", + "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==", + "dependencies": { + "uniq": "^1.0.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz", + "integrity": "sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.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==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "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==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "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/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.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==" + }, + "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==" + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "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==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==" + }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dependencies": { + "minipass": "^2.6.0" + } + }, + "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==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-pixels-frame-info-update": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/get-pixels-frame-info-update/-/get-pixels-frame-info-update-3.3.2.tgz", + "integrity": "sha512-LzVij57X/gK4Y6LpcDdqj+R9WCpD6Sv3ZH85GMA+S3xgPGCz81mHql4GiSnF4GijRjk7TE0ja2sDr8FFYKLe2g==", + "dependencies": { + "data-uri-to-buffer": "0.0.3", + "jpeg-js": "^0.3.2", + "mime-types": "^2.0.1", + "ndarray": "^1.0.13", + "ndarray-pack": "^1.1.1", + "node-bitmap": "0.0.1", + "omggif": "^1.0.5", + "parse-data-uri": "^0.2.0", + "pngjs": "^3.3.3", + "request": "^2.44.0", + "through": "^2.3.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/gif-encoder": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/gif-encoder/-/gif-encoder-0.4.1.tgz", + "integrity": "sha512-++rNGpDBgWQ9eXj9JfTBLHMUEd7lDOdzIvFyHQM9yL8ffxkcg4G6jWmsgu/r59Uq6nHc3wcVwtgy3geLnIWunQ==", + "dependencies": { + "readable-stream": "~1.1.9" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gif-encoder/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gif-encoder/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "engines": { + "node": ">= 0.4.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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/jpeg-js": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", + "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "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==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "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==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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==", + "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==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/multi-integer-range": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-integer-range/-/multi-integer-range-3.0.0.tgz", + "integrity": "sha512-uQzynjVJ8F7x5wjaK0g4Ybhy2TvO/pk96+YHyS5g1W4GuUEV6HMebZ8HcRwWgKIRCUT2MLbM5uCKwYcAqkS+8Q==" + }, + "node_modules/ndarray": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.18.tgz", + "integrity": "sha512-jUz6G+CIsEsqs2VlB1EvaQSAA0Jkf8YKm7eFBleKyhiQjYWzTxXqHzWEOm3jFoGCpxGh4DnPUYHB4ECWE+n9SQ==", + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, + "node_modules/ndarray-ops": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz", + "integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==", + "dependencies": { + "cwise-compiler": "^1.0.0" + } + }, + "node_modules/ndarray-pack": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ndarray-pack/-/ndarray-pack-1.2.1.tgz", + "integrity": "sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==", + "dependencies": { + "cwise-compiler": "^1.1.2", + "ndarray": "^1.0.13" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-bitmap": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/node-bitmap/-/node-bitmap-0.0.1.tgz", + "integrity": "sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==", + "engines": { + "node": ">=v0.6.5" + } + }, + "node_modules/node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==" + }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/nsfwjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nsfwjs/-/nsfwjs-2.4.2.tgz", + "integrity": "sha512-i4Pp2yt59qPQgeZFyg3wXFBX52uSeu/hkDoqdZfe+sILRxNBUu0VDogj7Lmqak0GlrXviS/wLiVeIx40IDUu7A==", + "dependencies": { + "@nsfw-filter/gif-frames": "1.0.2" + }, + "peerDependencies": { + "@tensorflow/tfjs": "^3.18.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-data-uri": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/parse-data-uri/-/parse-data-uri-0.2.0.tgz", + "integrity": "sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==", + "dependencies": { + "data-uri-to-buffer": "0.0.3" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "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==", + "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==" + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs-nozlib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pngjs-nozlib/-/pngjs-nozlib-1.0.0.tgz", + "integrity": "sha512-N1PggqLp9xDqwAoKvGohmZ3m4/N9xpY0nDZivFqQLcpLHmliHnCp9BuNCsOeqHWMuEEgFjpEaq9dZq6RZyy0fA==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "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==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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==", + "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==", + "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==", + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dependencies": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/through": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.4.tgz", + "integrity": "sha512-DwbmSAcABsMazNkLOJJSLRC3gfh4cPxUxJCn9npmvbcI6undhgoJ2ShvEOgZrW8BH62Gyr9jKboGbfFcmY5VsQ==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tsc-watch": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-6.0.4.tgz", + "integrity": "sha512-cHvbvhjO86w2aGlaHgSCeQRl+Aqw6X6XN4sQMPZKF88GoP30O+oTuh5lRIJr5pgFWrRpF1AgXnJJ2DoFEIPHyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.3.1" + }, + "bin": { + "tsc-watch": "dist/lib/tsc-watch.js" + }, + "engines": { + "node": ">=12.12.0" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "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==", + "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==" + }, + "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==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "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==" + }, + "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==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/api/package.json b/api/package.json new file mode 100644 index 00000000..b653d740 --- /dev/null +++ b/api/package.json @@ -0,0 +1,27 @@ +{ + "name": "api", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "scripts": { + "build": "npx tsc", + "start": "node dist/index.js", + "serve": "tsc-watch --outDir ./dist --onSuccess \"node .\"", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "devDependencies": { + "@types/express": "^4.17.19", + "@types/node": "^20.8.4", + "typescript": "^5.2.2" + }, + "dependencies": { + "@tensorflow/tfjs-node": "^4.11.0", + "express": "^4.18.2", + "nsfwjs": "^2.4.2", + "tsc-watch": "^6.0.4" + } +} diff --git a/api/tsconfig.json b/api/tsconfig.json new file mode 100644 index 00000000..fb43ed72 --- /dev/null +++ b/api/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "NodeNext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/archive/Levelling/Commands/Staff/level.ts b/archive/Levelling/Commands/Staff/level.ts deleted file mode 100644 index a067e84e..00000000 --- a/archive/Levelling/Commands/Staff/level.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; - -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('level') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) - .setDescription('Level managing command. Staff-only') - .addSubcommandGroup(subcommandGroup => - subcommandGroup - .setName('add') - .setDescription('Add XP') - .addSubcommand(subcommand => - subcommand - .setName('xp') - .setDescription('Add XP to user. Staff-only') - .addStringOption(user => - user - .setName('user') - .setDescription('The user ID to add level.') - .setRequired(true), - ) - .addIntegerOption(string => - string - .setName('xp') - .setDescription('XP to add.') - .setRequired(true), - ), - ), - ) - .addSubcommandGroup(subcommandGroup => - subcommandGroup - .setName('remove') - .setDescription('Remove XP') - .addSubcommand(subcommand => - subcommand - .setName('xp') - .setDescription('Remove XP to user. Staff-only') - .addStringOption(user => - user - .setName('user') - .setDescription('The user ID to remove level.') - .setRequired(true), - ) - .addIntegerOption(int => - int - .setName('xp') - .setDescription('XP to remove.') - .setRequired(true), - ), - ), - ) - .addSubcommandGroup(subcommandGroup => - subcommandGroup - .setName('set') - .setDescription('Set Level/XP') - .addSubcommand( - subcommand => - subcommand - .setName('level') - .setDescription('Set Level to user. Staff-only') - .addUserOption(user => - user - .setName('user') - .setDescription('The user ID to set level.') - .setRequired(true), - ) - .addIntegerOption(string => - string - .setName('level') - .setDescription('Levels to set.') - .setRequired(true), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('xp') - .setDescription('Set XP to user. Staff-only') - .addUserOption(user => - user - .setName('user') - .setDescription('The user ID to set level.') - .setRequired(true), - ) - .addIntegerOption(string => - string - .setName('xp') - .setDescription('XP to set.') - .setRequired(true), - ), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const subCommand = interaction.options.getSubcommand(); - require(`../../Scripts/level/${subCommand}`).execute(interaction); - }, -}; \ No newline at end of file diff --git a/archive/Levelling/Commands/leaderboard.ts b/archive/Levelling/Commands/leaderboard.ts deleted file mode 100644 index 556dbc3f..00000000 --- a/archive/Levelling/Commands/leaderboard.ts +++ /dev/null @@ -1,33 +0,0 @@ -import Levels from 'discord-xp'; -import { ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; -import { constants } from '../../../src/Utils/functions/utils'; - -export default { - data: new SlashCommandBuilder() - .setName('leaderboard') - .setDescription('See the network leaderboard'), - async execute(interaction: ChatInputCommandInteraction) { - const rawLeaderboard = await Levels.fetchLeaderboard(constants.mainGuilds.cbhq, 10); - const errorEmbed = new EmbedBuilder().setDescription('Nobody is in the leaderboard.'); - - if (rawLeaderboard.length > 0 == false) return await interaction.reply({ embeds: [errorEmbed] }); - - const leaderboard = await Levels.computeLeaderboard(interaction.client, rawLeaderboard, true); - - const leaderArr = leaderboard.map((e) => { - const postition = e.position === 1 ? '🥇' : e.position === 2 ? '🥈' : e.position === 3 ? '🥉' : `${e.position}.`; - return { - name: `\`${postition}\` ${e.username}#${e.discriminator}`, - value: `Level: ${e.level}\nXP: ${e.xp.toLocaleString()}\n`, - }; - }); - - const leaderboardEmbed = new EmbedBuilder() - .setColor(constants.colors.interchatBlue) - .setTitle('**Leaderboard**') - .setThumbnail(interaction.client.user?.avatarURL() as string) - .setFields(leaderArr); - - await interaction.reply({ embeds: [leaderboardEmbed], ephemeral: true }); - }, -}; diff --git a/archive/Levelling/Commands/rank.ts b/archive/Levelling/Commands/rank.ts deleted file mode 100644 index 3d74a339..00000000 --- a/archive/Levelling/Commands/rank.ts +++ /dev/null @@ -1,54 +0,0 @@ -import Levels from 'discord-xp'; -import canvacord from 'canvacord'; -import { EmbedBuilder, SlashCommandBuilder, AttachmentBuilder, ChatInputCommandInteraction } from 'discord.js'; -import { constants } from '../../../src/Utils/misc/utils'; - -export default { - data: new SlashCommandBuilder() - .setName('rank') - .setDescription('Shows the user\'s rank') - .addUserOption(option => - option - .setRequired(false) - .setName('user') - .setDescription('Check another user\'s rank'), - ), - async execute(interaction: ChatInputCommandInteraction) { - await interaction.deferReply(); - type LeaderboardUser = { - guildID: string; - userID: string; - xp: number; - level: number; - position: number; - username: string | null; - discriminator: string | null; - }; - const otheruser = interaction.options.getUser('user'); - const target = otheruser || interaction.user; - - const user: LeaderboardUser = await Levels.fetch(target.id, constants.mainGuilds.cbhq, true) as unknown as LeaderboardUser; - const errorEmbed = new EmbedBuilder().setDescription(`${user?.username || 'User'} doesn't have any xp.. Chat to gain some xp.`); - - if (!user) return await interaction.followUp({ embeds: [errorEmbed] }); - - const neededxp = Levels.xpFor(user.level + 1); - - const rankCard = new canvacord.Rank() - .setAvatar(target.avatarURL() as string) - .setBackground('IMAGE', 'https://cdn.discordapp.com/attachments/824616172569493504/999660076321210428/blob-scene-haikei.png') - .setCurrentXP(user.xp).setLevel(user.level || 0) - .setRequiredXP(neededxp).setRank(user.position) - .setProgressBar(constants.colors.interchatBlue, 'COLOR') - .setCustomStatusColor(constants.colors.interchatBlue) - .setUsername(target.username) - .setDiscriminator(target.discriminator); - - rankCard.build().then(async (data) => { - const attachment = new AttachmentBuilder(data, { name: 'rankcard.png' }); - return await interaction.followUp({ files: [attachment] }); - }); - - }, - -}; diff --git a/archive/Levelling/Scripts/level/level.ts b/archive/Levelling/Scripts/level/level.ts deleted file mode 100644 index 285219a6..00000000 --- a/archive/Levelling/Scripts/level/level.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import Levels from 'discord-xp'; -import { constants } from '../../../../src/Utils/functions/utils'; - -module.exports = { - async execute(interaction: ChatInputCommandInteraction) { - if (interaction.inCachedGuild()) { - const subcommandGroup = interaction.options.getSubcommandGroup(); - const userOpt = interaction.options.getMember('user'); - const level = interaction.options.getInteger('level'); - const user = await interaction.client.users.fetch(String(userOpt?.id)); - - if (subcommandGroup == 'set') { - Levels.setLevel(String(userOpt?.id), constants.mainGuilds.cbhq, Number(level)); - interaction.reply(`I have set ${level} Level(s) to ${user.username}`); - } - } - }, -}; \ No newline at end of file diff --git a/archive/Levelling/Scripts/level/xp.ts b/archive/Levelling/Scripts/level/xp.ts deleted file mode 100644 index 55069e3a..00000000 --- a/archive/Levelling/Scripts/level/xp.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Levels from 'discord-xp'; -import { ChatInputCommandInteraction } from 'discord.js'; -import { constants } from '../../../../src/Utils/functions/utils'; - -module.exports = { - async execute(interaction: ChatInputCommandInteraction) { - const subcommandGroup = interaction.options.getSubcommandGroup(); - const userOpt = interaction.options.getString('user'); - const xp = interaction.options.getInteger('xp'); - - const user = await interaction.client.users.fetch(String(userOpt)); - const userData = await Levels.fetch(String(userOpt), constants.mainGuilds.cbhq, true); - - if (Number(xp) < 0) return interaction.reply('You can\'t add negative XP.'); - - switch (subcommandGroup) { - case 'set': - Levels.setXp(String(userOpt), constants.mainGuilds.cbhq, Number(xp)); - interaction.reply(`I have set ${xp} XP to ${user.username}`); - break; - - case 'add': - Levels.appendXp(String(userOpt), constants.mainGuilds.cbhq, Number(xp)); - interaction.reply(`I have added ${xp} XP to ${user.username}`); - break; - - case 'remove': - if (userData.xp - Number(xp) < 0) return interaction.reply('You can\'t remove negative XP.'); - Levels.subtractXp(String(userOpt), constants.mainGuilds.cbhq, Number(xp)); - interaction.reply(`I have removed ${xp} XP from ${user.username}`); - break; - - default: - interaction.reply('Invalid subcommand.'); - break; - } - }, -}; diff --git a/archive/Levelling/Scripts/message/levelling.ts b/archive/Levelling/Scripts/message/levelling.ts deleted file mode 100644 index a381772c..00000000 --- a/archive/Levelling/Scripts/message/levelling.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Levels from 'discord-xp'; -import { Message } from 'discord.js'; -import { constants } from '../../../../src/Utils/functions/utils'; - - -module.exports = { - async execute(message: Message) { - const randomxp = Math.floor(Math.random() * 10) + 1; - const haslevelxp = await Levels.appendXp(message.author.id, constants.mainGuilds.cbhq, randomxp); - if (haslevelxp) { - const user = await Levels.fetch(message.author.id, constants.mainGuilds.cbhq); - message.reply(`🎉 Congrats you just levelled up to **${user.level}**!`); - } - }, -}; \ No newline at end of file diff --git a/archive/Levelling/index.ts b/archive/Levelling/index.ts deleted file mode 100644 index bf646510..00000000 --- a/archive/Levelling/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import Levels from 'discord-xp'; - -// this was in main file -Levels.setURL(process.env.MONGODB_URI as string); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a0aba5b8..215386c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,48 +1,47 @@ { "name": "interchat", - "version": "3.13.0", + "version": "3.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "interchat", - "version": "3.13.0", + "version": "3.14.0", "license": "AGPL-3.0-or-later", "dependencies": { - "@prisma/client": "^5.4.1", + "@prisma/client": "^5.4.2", "@sentry/node": "^7.73.0", - "@top-gg/sdk": "^3.1.6", + "@tensorflow/tfjs-node": "^4.11.0", "@translate-tools/core": "^1.0.0", "common-tags": "^1.8.2", - "discord-arts": "^0.4.5", + "discord-arts": "^0.5.7", + "discord-hybrid-sharding": "^2.1.3", "discord.js": "^14.13.0", "dotenv": "^16.3.1", "lodash": "^4.17.21", - "node-schedule": "^2.1.1", + "nsfwjs": "^2.4.2", "parse-duration": "^1.1.0", - "winston": "^3.10.0" + "winston": "^3.11.0" }, "devDependencies": { "@sentry/cli": "^2.21.2", "@types/common-tags": "^1.8.2", "@types/lodash": "^4.14.199", - "@types/node": "^20.8.3", - "@types/node-schedule": "^2.1.1", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", + "@types/node": "^20.8.4", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.51.0", "husky": "^8.0.3", "lint-staged": "^14.0.1", "prettier": "^3.0.3", - "prisma": "^5.4.1", + "prisma": "^5.4.2", "standard-version": "^9.5.0", "tsc-watch": "^6.0.4", "typescript": "^5.2.2" }, "engines": { - "node": ">=18.15.0", - "npm": ">=9.5.0" + "node": ">=18.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -532,14 +531,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "engines": { - "node": ">=14" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -610,6 +601,113 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", + "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@napi-rs/canvas": { "version": "0.1.44", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.44.tgz", @@ -803,13 +901,40 @@ "node": ">= 8" } }, + "node_modules/@nsfw-filter/gif-frames": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@nsfw-filter/gif-frames/-/gif-frames-1.0.2.tgz", + "integrity": "sha512-XZrbJWEN8YfVla5i+PD4Wj51rRlJ8OgnXiPjjOt/OsrbsCR9GZRD4jr953oNWcwiRaoIcOCFWQNMQukO7Yb1dA==", + "dependencies": { + "@nsfw-filter/save-pixels": "^2.3.4", + "get-pixels-frame-info-update": "3.3.2", + "multi-integer-range": "3.0.0" + } + }, + "node_modules/@nsfw-filter/save-pixels": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@nsfw-filter/save-pixels/-/save-pixels-2.3.4.tgz", + "integrity": "sha512-dRZXwrXadMvxwJYKChrDBqC6GNvxVqlmdkyvZJO5DV65qyBsHZw8bPg9CnX7EgpxGl6+4ba/MAdHDLxs2XoD0Q==", + "dependencies": { + "gif-encoder": "0.4.1", + "ndarray": "1.0.18", + "ndarray-ops": "1.2.2", + "pngjs-nozlib": "1.0.0", + "through": "2.3.4" + } + }, + "node_modules/@nsfw-filter/save-pixels/node_modules/through": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.4.tgz", + "integrity": "sha512-DwbmSAcABsMazNkLOJJSLRC3gfh4cPxUxJCn9npmvbcI6undhgoJ2ShvEOgZrW8BH62Gyr9jKboGbfFcmY5VsQ==" + }, "node_modules/@prisma/client": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.1.tgz", - "integrity": "sha512-xyD0DJ3gRNfLbPsC+YfMBBuLJtZKQfy1OD2qU/PZg+HKrr7SO+09174LMeTlWP0YF2wca9LxtVd4HnAiB5ketQ==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.2.tgz", + "integrity": "sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "5.4.1-1.2f302df92bd8945e20ad4595a73def5b96afa54f" + "@prisma/engines-version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" }, "engines": { "node": ">=16.13" @@ -824,16 +949,16 @@ } }, "node_modules/@prisma/engines": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.4.1.tgz", - "integrity": "sha512-vJTdY4la/5V3N7SFvWRmSMUh4mIQnyb/MNoDjzVbh9iLmEC+uEykj/1GPviVsorvfz7DbYSQC4RiwmlEpTEvGA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.4.2.tgz", + "integrity": "sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==", "devOptional": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "5.4.1-1.2f302df92bd8945e20ad4595a73def5b96afa54f", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-1.2f302df92bd8945e20ad4595a73def5b96afa54f.tgz", - "integrity": "sha512-+nUQM/y8C+1GG5Ioeqcu6itFslCfxvQSAUVSMC9XM2G2Fcq0F4Afnp6m0pXF6X6iUBWen7jZBPmM9Qlq4Nr3/A==" + "version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz", + "integrity": "sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA==" }, "node_modules/@sapphire/async-queue": { "version": "1.5.0", @@ -913,53 +1038,545 @@ "node": ">=8" } }, - "node_modules/@sentry/node": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.73.0.tgz", - "integrity": "sha512-i50bRfmgkRRx0XXUbg9jGD/RuznDJxJXc4rBILhoJuhl+BjRIaoXA3ayplfJn8JLZxsNh75uJaCq4IUK70SORw==", + "node_modules/@sentry/node": { + "version": "7.73.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.73.0.tgz", + "integrity": "sha512-i50bRfmgkRRx0XXUbg9jGD/RuznDJxJXc4rBILhoJuhl+BjRIaoXA3ayplfJn8JLZxsNh75uJaCq4IUK70SORw==", + "dependencies": { + "@sentry-internal/tracing": "7.73.0", + "@sentry/core": "7.73.0", + "@sentry/types": "7.73.0", + "@sentry/utils": "7.73.0", + "cookie": "^0.5.0", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.73.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.73.0.tgz", + "integrity": "sha512-/v8++bly8jW7r4cP2wswYiiVpn7eLLcqwnfPUMeCQze4zj3F3nTRIKc9BGHzU0V+fhHa3RwRC2ksqTGq1oJMDg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.73.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.73.0.tgz", + "integrity": "sha512-h3ZK/qpf4k76FhJV9uiSbvMz3V/0Ovy94C+5/9UgPMVCJXFmVsdw8n/dwANJ7LupVPfYP23xFGgebDMFlK1/2w==", + "dependencies": { + "@sentry/types": "7.73.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tensorflow/tfjs": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-3.21.0.tgz", + "integrity": "sha512-khcARd3/872llL/oF4ouR40qlT71mylU66PGT8kHP/GJ5YKj44sv8lDRjU7lOVlJK7jsJFWEsNVHI3eMc/GWNQ==", + "peer": true, + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.21.0", + "@tensorflow/tfjs-backend-webgl": "3.21.0", + "@tensorflow/tfjs-converter": "3.21.0", + "@tensorflow/tfjs-core": "3.21.0", + "@tensorflow/tfjs-data": "3.21.0", + "@tensorflow/tfjs-layers": "3.21.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz", + "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==", + "peer": true, + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.21.0.tgz", + "integrity": "sha512-N4zitIAT9IX8B8oe489qM3f3VcESxGZIZvHmVP8varOQakTvTX859aaPo1s8hK1qCy4BjSGbweooZe4U8D4kTQ==", + "peer": true, + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "3.21.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@types/webgl2": "0.0.6", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.21.0.tgz", + "integrity": "sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==", + "peer": true, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.21.0.tgz", + "integrity": "sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==", + "peer": true, + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "@types/webgl-ext": "0.0.30", + "@webgpu/types": "0.1.16", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-3.21.0.tgz", + "integrity": "sha512-eFLfw2wIcFNxnP2Iv/SnVlihehzKMumk1b5Prcx1ixk/SbkCo5u0Lt7OVOWaEOKVqvB2sT+dJcTjAh6lrCC/QA==", + "peer": true, + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-data/node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-3.21.0.tgz", + "integrity": "sha512-CMVXsraakXgnXEnqD9QbtResA7nvV7Jz20pGmjFIodcQkClgmFFhdCG5N+zlVRHEz7VKG2OyfhltZ0dBq/OAhA==", + "peer": true, + "peerDependencies": { + "@tensorflow/tfjs-core": "3.21.0" + } + }, + "node_modules/@tensorflow/tfjs-node": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.11.0.tgz", + "integrity": "sha512-dKMabHsyXEjVM9hSPITa9s7+SA7mqIRCN3ITbOoiVQ4JVlpSg2sffORWOQaRbYISP7F+l6RFiw0EB7t5vCXPzg==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "1.0.9", + "@tensorflow/tfjs": "4.11.0", + "adm-zip": "^0.5.2", + "google-protobuf": "^3.9.2", + "https-proxy-agent": "^2.2.1", + "progress": "^2.0.0", + "rimraf": "^2.6.2", + "tar": "^4.4.6" + }, + "engines": { + "node": ">=8.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.11.0.tgz", + "integrity": "sha512-s6Vbz3IvMz2zNbH8/VptpRXzkwVjmuzT48esYLXJxMKtTcob4m5Srdxo7B+eJSDrWYkutXruiivaWmihFmu5rA==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.11.0", + "@tensorflow/tfjs-backend-webgl": "4.11.0", + "@tensorflow/tfjs-converter": "4.11.0", + "@tensorflow/tfjs-core": "4.11.0", + "@tensorflow/tfjs-data": "4.11.0", + "@tensorflow/tfjs-layers": "4.11.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.11.0.tgz", + "integrity": "sha512-2zmGX9MuR8AwscSGOybz4fBOFgQDnj+ZCWGkLxDzbKecy9GxuilukT46xB2zU0kSq7Mf3ncfE/9eUEy6a7ZDqQ==", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.11.0.tgz", + "integrity": "sha512-sM/B65u+1T3U+Ctiq1fn5j6VmiLEZW7BpuSa3ZXDXtIS07MoZ2FTuO8BMudxEY4xGpTyoOzqTOGT9BaGO3qrWg==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.11.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-converter": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.11.0.tgz", + "integrity": "sha512-j2JEVwkqh+pyin+sxUiNUG7QOIU2S0+5SzN8LFXHlR90/bPvC2qiaaSPYdGG/BYidFc27QCHD3obBXrb1EE/ow==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-core": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.11.0.tgz", + "integrity": "sha512-t0mCNIco8wg6aZdHWT1d6ZuKtbbdY5y871ELWLSUA1+grXDvvaroHYh5eeJexJYXeg+EQ0/hzB0G8nLsLjlyVQ==", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.30", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.1", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.1.tgz", + "integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==" + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-data": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.11.0.tgz", + "integrity": "sha512-8E6CVpd7kxRFtVL7kvz6WF5jH18pNN2wEcm2yA87xq37JwcRsIPTkrmfyqCHlJZmiWn3RQbP59Sl05gbBnFo5w==", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-layers": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.11.0.tgz", + "integrity": "sha512-ErVqwxjpu2YM3uJRj2o5GbBTYViUwnqOb0wKWuCVukVmGeWrUzf1X00Ky3dP4xfilfAvq+B26dg7QN4YNHeaKg==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.11.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/@webgpu/types": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.30.tgz", + "integrity": "sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==" + }, + "node_modules/@tensorflow/tfjs-node/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@tensorflow/tfjs-node/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/@tensorflow/tfjs-node/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@tensorflow/tfjs-node/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==" + }, + "node_modules/@tensorflow/tfjs-node/node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@tensorflow/tfjs-node/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@tensorflow/tfjs-node/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/@tensorflow/tfjs-node/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tensorflow/tfjs/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==", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "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/@tensorflow/tfjs/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==", + "peer": true, "dependencies": { - "@sentry-internal/tracing": "7.73.0", - "@sentry/core": "7.73.0", - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", - "cookie": "^0.5.0", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^2.4.1 || ^1.9.3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/@sentry/types": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.73.0.tgz", - "integrity": "sha512-/v8++bly8jW7r4cP2wswYiiVpn7eLLcqwnfPUMeCQze4zj3F3nTRIKc9BGHzU0V+fhHa3RwRC2ksqTGq1oJMDg==", + "node_modules/@tensorflow/tfjs/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==", + "peer": true + }, + "node_modules/@tensorflow/tfjs/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==", + "peer": true, "engines": { "node": ">=8" } }, - "node_modules/@sentry/utils": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.73.0.tgz", - "integrity": "sha512-h3ZK/qpf4k76FhJV9uiSbvMz3V/0Ovy94C+5/9UgPMVCJXFmVsdw8n/dwANJ7LupVPfYP23xFGgebDMFlK1/2w==", + "node_modules/@tensorflow/tfjs/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==", + "peer": true, "dependencies": { - "@sentry/types": "7.73.0", - "tslib": "^2.4.1 || ^1.9.3" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@top-gg/sdk": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@top-gg/sdk/-/sdk-3.1.6.tgz", - "integrity": "sha512-qWWYDKAwJHaKaA/5EyLYMzfR76MwCbmKVMSXTPMd9FZFPHuLWJHO+m7Q8dWpWtcnunHa6evRAwlB3p83cht7Ww==", - "dependencies": { - "raw-body": "^2.5.2", - "undici": "^5.23.0" - } - }, "node_modules/@translate-tools/core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@translate-tools/core/-/core-1.0.0.tgz", @@ -972,6 +1589,14 @@ "xpath": "^0.0.32" } }, + "node_modules/@translate-tools/core/node_modules/axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1018,6 +1643,11 @@ "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", "dev": true }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/minimist": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", @@ -1025,17 +1655,20 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.3.tgz", - "integrity": "sha512-jxiZQFpb+NlH5kjW49vXxvxTjeeqlbsnTAdBTKpzEdPs9itay7MscYXz3Fo9VYFEsfQ6LJFitHad3faerLAjCw==" + "version": "20.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", + "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "dependencies": { + "undici-types": "~5.25.1" + } }, - "node_modules/@types/node-schedule": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.1.tgz", - "integrity": "sha512-FaqkbBizA+DinA0XWtAhdbEXykUkkqzBWT4BSnhn71z9C+vvcDgNcHvTP59nBhMg3o39E/ZY8zB/AQ6/HGuRag==", - "dev": true, + "node_modules/@types/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==", "dependencies": { - "@types/node": "*" + "@types/node": "*", + "form-data": "^4.0.0" } }, "node_modules/@types/normalize-package-data": { @@ -1044,6 +1677,16 @@ "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", "dev": true }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" + }, + "node_modules/@types/seedrandom": { + "version": "2.4.31", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.31.tgz", + "integrity": "sha512-O8t5IyMqJ5qSxOR/UJ4hWL64ix05ofO7FV9IgMwVtUvHu7EsI8YyMJOg7SAWrWhDqizj1oxNZAGgfkCrhk7GTQ==" + }, "node_modules/@types/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", @@ -1055,6 +1698,18 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", "integrity": "sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g==" }, + "node_modules/@types/webgl-ext": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", + "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==", + "peer": true + }, + "node_modules/@types/webgl2": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", + "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==", + "peer": true + }, "node_modules/@types/ws": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", @@ -1064,16 +1719,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", + "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/type-utils": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1099,15 +1754,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", + "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", "debug": "^4.3.4" }, "engines": { @@ -1127,13 +1782,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", + "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1144,13 +1799,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", + "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/utils": "6.7.5", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1171,9 +1826,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", + "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1184,13 +1839,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", + "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1211,17 +1866,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", + "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", "semver": "^7.5.4" }, "engines": { @@ -1236,12 +1891,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", + "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.7.5", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1261,6 +1916,12 @@ "npm": ">=7.0.0" } }, + "node_modules/@webgpu/types": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", + "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==", + "peer": true + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -1269,6 +1930,11 @@ "node": ">=10.0.0" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1306,6 +1972,14 @@ "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", "dev": true }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "engines": { + "node": ">=6.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1365,7 +2039,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1382,6 +2055,23 @@ "node": ">=4" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1419,11 +2109,32 @@ "node": ">=0.10.0" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -1433,19 +2144,23 @@ "node": ">= 4.0.0" } }, - "node_modules/axios": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", - "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", - "dependencies": { - "follow-redirects": "^1.14.4" + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" } }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "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==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -1467,6 +2182,14 @@ } ] }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1482,7 +2205,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1541,14 +2263,6 @@ "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==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cachedir": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", @@ -1593,6 +2307,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1613,6 +2332,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -1716,7 +2440,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -1763,6 +2486,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -1778,6 +2509,17 @@ "text-hex": "1.0.x" } }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", @@ -1838,8 +2580,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "2.0.0", @@ -1856,6 +2597,11 @@ "typedarray": "^0.0.6" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/conventional-changelog": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", @@ -2131,11 +2877,21 @@ "node": ">= 0.6" } }, + "node_modules/core-js": { + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz", + "integrity": "sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==", + "hasInstallScript": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cosmiconfig": { "version": "8.3.6", @@ -2187,17 +2943,6 @@ "dev": true, "optional": true }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", - "dependencies": { - "luxon": "^3.2.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2212,6 +2957,14 @@ "node": ">= 8" } }, + "node_modules/cwise-compiler": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz", + "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==", + "dependencies": { + "uniq": "^1.0.0" + } + }, "node_modules/cz-conventional-changelog": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz", @@ -2241,6 +2994,22 @@ "node": ">=8" } }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz", + "integrity": "sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==" + }, "node_modules/dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -2332,14 +3101,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { - "node": ">= 0.8" + "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -2358,6 +3132,14 @@ "node": ">=8" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2395,14 +3177,22 @@ "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" }, "node_modules/discord-arts": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/discord-arts/-/discord-arts-0.4.5.tgz", - "integrity": "sha512-SXlNcgijeMtLPf5MEDmrKAqxLANg7jsbq8X+nJ9f0HFAGsZoCjxB660njOucN4Im4Owk/RrGuw1s1C/sTQq74w==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/discord-arts/-/discord-arts-0.5.7.tgz", + "integrity": "sha512-/HsFWfoF41EUyJ4uByKRLiJy2FRhXMKOzGw4P/D+E2gt9Sh9des2NRFVSrHAw3k3flwdjU4EN8e2EU+FDHqiTg==", "dependencies": { "@napi-rs/canvas": "^0.1.33", "node-fetch": "^2.6.7" } }, + "node_modules/discord-hybrid-sharding": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/discord-hybrid-sharding/-/discord-hybrid-sharding-2.1.3.tgz", + "integrity": "sha512-yPBjhvPpV1k+iOGUgohVhtQFpIHuspbq/wFa+VoGDqyujjjsZImDq2TXsBclnThDfMrsSIE47uf2aLMhm8QqBA==", + "dependencies": { + "node-fetch": "^2.6.7" + } + }, "node_modules/discord.js": { "version": "14.13.0", "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.13.0.tgz", @@ -2559,11 +3349,19 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enabled": { "version": "2.0.0", @@ -2579,11 +3377,23 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -2910,6 +3720,11 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -2924,6 +3739,14 @@ "node": ">=4" } }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.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", @@ -2960,8 +3783,7 @@ "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==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -3121,6 +3943,27 @@ } } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "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==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -3142,21 +3985,64 @@ "node": ">=10" } }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dependencies": { + "minipass": "^2.6.0" + } + }, "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==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-pixels-frame-info-update": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/get-pixels-frame-info-update/-/get-pixels-frame-info-update-3.3.2.tgz", + "integrity": "sha512-LzVij57X/gK4Y6LpcDdqj+R9WCpD6Sv3ZH85GMA+S3xgPGCz81mHql4GiSnF4GijRjk7TE0ja2sDr8FFYKLe2g==", + "dependencies": { + "data-uri-to-buffer": "0.0.3", + "jpeg-js": "^0.3.2", + "mime-types": "^2.0.1", + "ndarray": "^1.0.13", + "ndarray-pack": "^1.1.1", + "node-bitmap": "0.0.1", + "omggif": "^1.0.5", + "parse-data-uri": "^0.2.0", + "pngjs": "^3.3.3", + "request": "^2.44.0", + "through": "^2.3.4" + } + }, "node_modules/get-pkg-repo": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", @@ -3227,6 +4113,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/gif-encoder": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/gif-encoder/-/gif-encoder-0.4.1.tgz", + "integrity": "sha512-++rNGpDBgWQ9eXj9JfTBLHMUEd7lDOdzIvFyHQM9yL8ffxkcg4G6jWmsgu/r59Uq6nHc3wcVwtgy3geLnIWunQ==", + "dependencies": { + "readable-stream": "~1.1.9" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/gif-encoder/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/gif-encoder/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gif-encoder/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, "node_modules/git-raw-commits": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", @@ -3297,7 +4223,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3415,6 +4340,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3448,6 +4378,47 @@ "uglify-js": "^3.1.4" } }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-validator/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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/har-validator/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==" + }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -3475,6 +4446,11 @@ "node": ">=4" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -3499,19 +4475,18 @@ "node": ">=10" } }, - "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==", + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" }, "engines": { - "node": ">= 0.8" + "node": ">=0.8", + "npm": ">=1.3.7" } }, "node_modules/https-proxy-agent": { @@ -3554,6 +4529,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -3637,7 +4613,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3750,12 +4725,22 @@ "node": ">=8" } }, + "node_modules/iota-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", @@ -3870,6 +4855,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -3909,6 +4899,16 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/jpeg-js": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", + "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3927,6 +4927,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3945,6 +4950,11 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3961,8 +4971,7 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "node_modules/jsonfile": { "version": "6.1.0", @@ -4001,6 +5010,20 @@ "node": "*" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -4584,10 +5607,10 @@ "triple-beam": "^1.3.0" } }, - "node_modules/long-timeout": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", - "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/longest": { "version": "2.0.1", @@ -4607,7 +5630,6 @@ "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, "dependencies": { "yallist": "^4.0.0" }, @@ -4615,19 +5637,33 @@ "node": ">=10" } }, - "node_modules/luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==", - "engines": { - "node": ">=12" - } - }, "node_modules/magic-bytes.js": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.5.0.tgz", "integrity": "sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==" }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -4853,6 +5889,25 @@ "node": ">=8.6" } }, + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -4878,7 +5933,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4890,7 +5944,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4909,6 +5962,39 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -4923,6 +6009,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/multi-integer-range": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-integer-range/-/multi-integer-range-3.0.0.tgz", + "integrity": "sha512-uQzynjVJ8F7x5wjaK0g4Ybhy2TvO/pk96+YHyS5g1W4GuUEV6HMebZ8HcRwWgKIRCUT2MLbM5uCKwYcAqkS+8Q==" + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -4935,12 +6026,46 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/ndarray": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.18.tgz", + "integrity": "sha512-jUz6G+CIsEsqs2VlB1EvaQSAA0Jkf8YKm7eFBleKyhiQjYWzTxXqHzWEOm3jFoGCpxGh4DnPUYHB4ECWE+n9SQ==", + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, + "node_modules/ndarray-ops": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz", + "integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==", + "dependencies": { + "cwise-compiler": "^1.0.0" + } + }, + "node_modules/ndarray-pack": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ndarray-pack/-/ndarray-pack-1.2.1.tgz", + "integrity": "sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==", + "dependencies": { + "cwise-compiler": "^1.1.2", + "ndarray": "^1.0.13" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-bitmap": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/node-bitmap/-/node-bitmap-0.0.1.tgz", + "integrity": "sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==", + "engines": { + "node": ">=v0.6.5" + } + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -4966,14 +6091,15 @@ } } }, - "node_modules/node-schedule": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", - "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "dependencies": { - "cron-parser": "^4.2.0", - "long-timeout": "0.1.1", - "sorted-array-functions": "^1.3.0" + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" }, "engines": { "node": ">=6" @@ -5021,11 +6147,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/nsfwjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nsfwjs/-/nsfwjs-2.4.2.tgz", + "integrity": "sha512-i4Pp2yt59qPQgeZFyg3wXFBX52uSeu/hkDoqdZfe+sILRxNBUu0VDogj7Lmqak0GlrXviS/wLiVeIx40IDUu7A==", + "dependencies": { + "@nsfw-filter/gif-frames": "1.0.2" + }, + "peerDependencies": { + "@tensorflow/tfjs": "^3.18.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, "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==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -5223,6 +6391,14 @@ "node": ">=6" } }, + "node_modules/parse-data-uri": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/parse-data-uri/-/parse-data-uri-0.2.0.tgz", + "integrity": "sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==", + "dependencies": { + "data-uri-to-buffer": "0.0.3" + } + }, "node_modules/parse-duration": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", @@ -5268,7 +6444,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5306,6 +6481,11 @@ "through": "~2.3" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -5339,6 +6519,23 @@ "node": ">=0.10.0" } }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs-nozlib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pngjs-nozlib/-/pngjs-nozlib-1.0.0.tgz", + "integrity": "sha512-N1PggqLp9xDqwAoKvGohmZ3m4/N9xpY0nDZivFqQLcpLHmliHnCp9BuNCsOeqHWMuEEgFjpEaq9dZq6RZyy0fA==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5364,13 +6561,13 @@ } }, "node_modules/prisma": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.4.1.tgz", - "integrity": "sha512-op9PmU8Bcw5dNAas82wBYTG0yHnpq9/O3bhxbDBrNzwZTwBqsVCxxYRLf6wHNh9HVaDGhgjjHlu1+BcW8qdnBg==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.4.2.tgz", + "integrity": "sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.4.1" + "@prisma/engines": "5.4.2" }, "bin": { "prisma": "build/index.js" @@ -5389,7 +6586,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -5415,11 +6611,15 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } @@ -5434,6 +6634,14 @@ "teleport": ">=0.2.0" } }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/query-string": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", @@ -5480,20 +6688,6 @@ "node": ">=8" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "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/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -5662,11 +6856,59 @@ "node": ">=8" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5791,7 +7033,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -5875,11 +7116,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5890,10 +7135,10 @@ "node": ">=10" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -5919,8 +7164,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -5972,11 +7216,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/sorted-array-functions": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", - "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6047,6 +7286,35 @@ "readable-stream": "^3.0.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -6083,14 +7351,6 @@ "node": ">=10" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -6137,7 +7397,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6151,7 +7410,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -6167,7 +7425,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6244,6 +7501,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dependencies": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -6267,8 +7546,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/through2": { "version": "4.0.2", @@ -6303,12 +7581,16 @@ "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==", + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, "engines": { - "node": ">=0.6" + "node": ">=0.8" } }, "node_modules/tr46": { @@ -6420,6 +7702,22 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6476,16 +7774,15 @@ "node": ">=0.8.0" } }, - "node_modules/undici": { - "version": "5.25.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.25.4.tgz", - "integrity": "sha512-450yJxT29qKMf3aoudzFpIciqpx6Pji3hEWaXqXmanbXF58LTAGCKxcJjxMXWu3iG+Mudgo3ZUfDB6YDFd/dAw==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" }, "node_modules/universalify": { "version": "2.0.0", @@ -6496,19 +7793,10 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "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==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -6518,6 +7806,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -6535,6 +7832,24 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -6573,12 +7888,20 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/winston": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", - "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", + "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", @@ -6607,6 +7930,14 @@ "node": ">= 6.4.0" } }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/winston/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6637,7 +7968,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6654,7 +7984,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -6669,7 +7998,6 @@ "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, "dependencies": { "color-name": "~1.1.4" }, @@ -6680,14 +8008,12 @@ "node_modules/wrap-ansi/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 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "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==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.14.2", @@ -6730,7 +8056,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -6738,8 +8063,7 @@ "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 + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.3.1", @@ -6754,7 +8078,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -6772,7 +8095,6 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, "engines": { "node": ">=10" } diff --git a/package.json b/package.json index b9ee7d9c..a97164ed 100644 --- a/package.json +++ b/package.json @@ -1,66 +1,50 @@ { "name": "interchat", - "version": "3.13.0", + "version": "3.14.0", "description": "A growing Discord bot which provides inter-server chat!", "main": "build/index.js", - "maintainers": [ - { - "name": "Supreme1707" - }, - { - "name": "dev-737" - } - ], - "contributors": [ - { - "name": "Chanakan55991", - "url": "https://github.com/Chanakan55991" - } - ], "license": "AGPL-3.0-or-later", "scripts": { "start": "node .", "build": "tsc --build && npm run sentry:sourcemaps", "build:dev": "tsc --build", "dev": "tsc-watch --outDir ./build --onSuccess \"node .\"", - "deploy": "node build/Utils/deploy-commands.js", + "register:commands": "node build/utils/RegisterCommands.js", "release": "standard-version", "lint": "eslint --cache --fix .", "prepare": "husky install", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org interchat --project interchat-production ./build && sentry-cli sourcemaps upload --org interchat --project interchat-production ./build" }, "engines": { - "node": ">=18.15.0", - "npm": ">=9.5.0" + "node": ">=18.0.0" }, + "type": "module", "dependencies": { - "@prisma/client": "^5.4.1", + "@prisma/client": "^5.4.2", "@sentry/node": "^7.73.0", - "@top-gg/sdk": "^3.1.6", "@translate-tools/core": "^1.0.0", "common-tags": "^1.8.2", - "discord-arts": "^0.4.5", + "discord-arts": "^0.5.7", + "discord-hybrid-sharding": "^2.1.3", "discord.js": "^14.13.0", "dotenv": "^16.3.1", "lodash": "^4.17.21", - "node-schedule": "^2.1.1", "parse-duration": "^1.1.0", - "winston": "^3.10.0" + "winston": "^3.11.0" }, "devDependencies": { "@sentry/cli": "^2.21.2", "@types/common-tags": "^1.8.2", "@types/lodash": "^4.14.199", - "@types/node": "^20.8.3", - "@types/node-schedule": "^2.1.1", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", + "@types/node": "^20.8.4", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.51.0", "husky": "^8.0.3", "lint-staged": "^14.0.1", "prettier": "^3.0.3", - "prisma": "^5.4.1", + "prisma": "^5.4.2", "standard-version": "^9.5.0", "tsc-watch": "^6.0.4", "typescript": "^5.2.2" diff --git a/src/Commands/Apps/blacklist.ts b/src/Commands/Apps/blacklist.ts deleted file mode 100644 index b8c34c9c..00000000 --- a/src/Commands/Apps/blacklist.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ActionRowBuilder, ApplicationCommandType, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, EmbedBuilder, MessageContextMenuCommandInteraction, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import { captureException } from '@sentry/node'; -import { addServerBlacklist, addUserBlacklist, notifyBlacklist, scheduleUnblacklist } from '../../Utils/blacklist'; -import emojis from '../../Utils/JSON/emoji.json'; -import parse from 'parse-duration'; - -export default { - description: 'Blacklist the user or server that sent the message from the hub.', - data: new ContextMenuCommandBuilder() - .setName('Add to Blacklist') - .setType(ApplicationCommandType.Message) - .setDMPermission(false), - async execute(interaction: MessageContextMenuCommandInteraction) { - const db = getDb(); - const messageInDb = await db.messageData.findFirst({ where: { - channelAndMessageIds: { some: { messageId: interaction.targetId } }, - hub: { - OR: [ - { moderators: { some: { userId: interaction.user.id } } }, - { ownerId: interaction.user.id }, - ], - }, - }, - }); - - if (!messageInDb) { - interaction.reply({ - content: - 'This message was not sent in a network, has expired or you lack required permissions to perform this action.', - ephemeral: true, - }); - return; - } - - const embed = new EmbedBuilder() - .setTitle('Blacklist') - .setDescription('Blacklist this server or user from this hub, preventing their messages from being sent.') - .setColor('Blurple'); - - const buttons = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId('blacklist_user') - .setLabel('Blacklist User') - .setStyle(ButtonStyle.Secondary) - .setEmoji('👤'), - new ButtonBuilder() - .setCustomId('blacklist_server') - .setLabel('Blacklist Server') - .setStyle(ButtonStyle.Secondary) - .setEmoji('🏠'), - ); - - const reply = await interaction.reply({ embeds: [embed], components: [buttons] }); - - const collector = reply.createMessageComponentCollector({ filter: (i) => i.user.id === interaction.user.id, idle: 60000 }); - collector.on('collect', async (i) => { - if (!messageInDb.hubId) return; - - const modal = new ModalBuilder() - .setTitle('Blacklist') - .setCustomId(i.id) - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('reason') - .setLabel('Reason') - .setPlaceholder('What is the reason for this blacklist?') - .setStyle(TextInputStyle.Paragraph) - .setMaxLength(500), - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('duration') - .setLabel('Duration') - .setPlaceholder('Duration of the blacklist. Eg. 1d 2h 3m') - .setStyle(TextInputStyle.Short) - .setMinLength(2) - .setRequired(false), - ), - ); - - await i.showModal(modal); - - const modalResp = await i.awaitModalSubmit({ time: 60000 }) - .catch((e) => { - !e.message.includes('with reason: time') ? captureException(e) : null; - return null; - }); - - if (modalResp?.customId !== i.id) return; - - await modalResp.deferUpdate(); - - const reason = modalResp.fields.getTextInputValue('reason'); - const duration = parse(modalResp.fields.getTextInputValue('duration')); - const expires = duration ? new Date(Date.now() + duration) : undefined; - - const successEmbed = new EmbedBuilder() - .setColor('Green') - .addFields( - { - name: 'Reason', - value: reason ? reason : 'No reason provided.', - inline: true, - }, - { - name: 'Expires', - value: expires ? `` : 'Never.', - inline: true, - }, - ); - - - if (i.customId === 'blacklist_user') { - const user = await i.client.users.fetch(messageInDb.authorId).catch(() => null); - successEmbed.setDescription(`${emojis.normal.tick} **${user?.username}** has been successfully blacklisted!`); - await addUserBlacklist(messageInDb.hubId, i.user, messageInDb.authorId, reason, expires); - - if (expires) scheduleUnblacklist('user', i.client, messageInDb.authorId, messageInDb.hubId, expires); - if (user) notifyBlacklist(user, messageInDb.hubId, expires, reason).catch(() => null); - - await modalResp.editReply({ embeds: [successEmbed], components: [] }); - } - - else if (i.customId === 'blacklist_server') { - successEmbed.setDescription(`${emojis.normal.tick} **${i.client.guilds.cache.get(messageInDb.serverId)?.name}** has been successfully blacklisted!`); - await addServerBlacklist(messageInDb.serverId, i.user, messageInDb.hubId, reason, expires); - await db.connectedList.deleteMany({ where: { serverId: messageInDb.serverId, hubId: messageInDb.hubId } }); - - if (expires) scheduleUnblacklist('server', i.client, messageInDb.serverId, messageInDb.hubId, expires); - - // TODO: Notify server of blacklist - - await modalResp.editReply({ embeds: [successEmbed], components: [] }); - } - }); - }, -}; diff --git a/src/Commands/Apps/deleteMsg.ts b/src/Commands/Apps/deleteMsg.ts deleted file mode 100644 index 99c920df..00000000 --- a/src/Commands/Apps/deleteMsg.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ContextMenuCommandBuilder, ApplicationCommandType, MessageContextMenuCommandInteraction } from 'discord.js'; -import { getDb, checkIfStaff } from '../../Utils/utils'; -import { networkMessageDelete } from '../../Scripts/networkLogs/msgDelete'; -import emojis from '../../Utils/JSON/emoji.json'; - - -export default { - description: 'Delete a message that was sent in the network.', - data: new ContextMenuCommandBuilder() - .setName('Delete Message') - .setType(ApplicationCommandType.Message), - async execute(interaction: MessageContextMenuCommandInteraction) { - await interaction.deferReply({ ephemeral: true }); - - const db = getDb(); - const emoji = emojis.normal; - const messageInDb = await db?.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: { equals: interaction.targetId } } } }, - include: { hub: true }, - }); - - if (!messageInDb) return await interaction.editReply('Unknown Message. If it has been sent in the past minute, please wait few more seconds and try again.'); - - const interchatStaff = checkIfStaff(interaction.user.id); - if ( - !interchatStaff && - !messageInDb.hub?.moderators.find((m) => m.userId === interaction.user.id) && - messageInDb.hub?.ownerId !== interaction.user.id && - interaction.user.id !== messageInDb.authorId - ) return await interaction.editReply(`${emoji.no} You are not the author of this message.`); - - - const results = messageInDb.channelAndMessageIds.map(async (element) => { - // fetch each channel the message was sent to - const channel = await interaction.client.channels.fetch(element.channelId).catch(() => null); - if (!channel?.isTextBased()) return false; - - // fetch the message from the channel and the webhook from the message - const message = await channel.messages.fetch(element.messageId).catch(() => null); - const webhook = await message?.fetchWebhook()?.catch(() => null); - - if (webhook?.owner?.id !== interaction.client.user?.id) return false; - - // finally, delete the message - return await webhook?.deleteMessage(element.messageId, channel.isThread() ? channel.id : undefined) - .then(() => true) - .catch(() => false); - }); - - const resultsArray = await Promise.all(results); - const deleted = resultsArray.reduce((acc, cur) => acc + (cur ? 1 : 0), 0); - await interaction.editReply(`${emoji.yes} Your message has been deleted from __**${deleted}/${resultsArray.length}**__ servers.`).catch(() => null); - - // log the deleted message for moderation purposes - if (interaction.inCachedGuild()) networkMessageDelete(interaction.member, interaction.targetMessage); - }, -}; diff --git a/src/Commands/Apps/editMsg.ts b/src/Commands/Apps/editMsg.ts deleted file mode 100644 index 7a923d5e..00000000 --- a/src/Commands/Apps/editMsg.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { - ContextMenuCommandBuilder, - MessageContextMenuCommandInteraction, - ApplicationCommandType, - ModalBuilder, - ActionRowBuilder, - TextInputBuilder, - TextInputStyle, - EmbedBuilder, -} from 'discord.js'; -import { networkMsgUpdate } from '../../Scripts/networkLogs/msgUpdate'; -import { checkIfStaff, getDb, getGuildName, replaceLinks, topgg } from '../../Utils/utils'; -import { captureException } from '@sentry/node'; -import { HubSettingsBitField } from '../../Utils/hubSettingsBitfield'; -import wordFiler from '../../Utils/wordFilter'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - description: 'Edit a message that was sent in the network.', - data: new ContextMenuCommandBuilder() - .setName('Edit Message') - .setType(ApplicationCommandType.Message), - async execute(interaction: MessageContextMenuCommandInteraction) { - const target = interaction.targetMessage; - - if (!(await topgg.hasVoted(interaction.user.id)) && !checkIfStaff(interaction.user.id)) { - await interaction.reply({ - content: `${emojis.normal.no} You must [vote]() to use this command.`, - ephemeral: true, - }); - return; - } - - const db = getDb(); - const messageInDb = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: { equals: target.id } } } }, - include: { hub: true }, - }); - - if (!messageInDb) { - await interaction.reply({ - content: 'This message has expired. If not, please wait a few seconds and try again.', - ephemeral: true, - }); - return; - } - else if (interaction.user.id != messageInDb?.authorId) { - await interaction.reply({ - content: 'You are not the author of this message.', - ephemeral: true, - }); - return; - } - - const modal = new ModalBuilder() - .setCustomId(interaction.id) - .setTitle('Edit Message') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setRequired(true) - .setCustomId('newMessage') - .setStyle(TextInputStyle.Paragraph) - .setLabel('Please enter your new message.') - .setValue(`${target.content || target.embeds[0]?.description}`) - .setMaxLength(950), - ), - ); - - await interaction.showModal(modal); - - const editInteraction = await interaction - .awaitModalSubmit({ - filter: (i) => i.user.id === interaction.user.id && i.customId === modal.data.custom_id, - time: 30_000, - }) - .catch((e) => { - if (e.message.includes('reason: time')) return null; - captureException(e, { user: { username: interaction.user.username } }); - return null; - }); - - if (!editInteraction) return; - // defer it because it takes a while to edit the message - await editInteraction.deferReply({ ephemeral: true }); - - // get the new message input by user - const userInput = editInteraction.fields.getTextInputValue('newMessage'); - const hubSettings = new HubSettingsBitField(messageInDb.hub?.settings); - const newMessage = hubSettings.has('HideLinks') ? replaceLinks(userInput) : userInput; - const censoredNewMessage = wordFiler.censor(newMessage); - - if ( - newMessage.includes('discord.gg') || - newMessage.includes('discord.com/invite') || - newMessage.includes('dsc.gg') - ) { - editInteraction.reply({ - content: `${emojis.normal.no} Do not advertise or promote servers in the network. Set an invite in \`/network manage\` instead!`, - ephemeral: true, - }); - return; - } - - // if the message being edited is in compact mode - // then we create a new embed with the new message and old reply - // else we just use the old embed and replace the description - const newEmbed = target.content - ? new EmbedBuilder() - .setAuthor({ name: target.author.username, iconURL: target.author.displayAvatarURL() }) - .setDescription(newMessage) - .setColor(target.member?.displayHexColor ?? 'Random') - .addFields( - target.embeds[0] - ? [{ name: 'Reply-to', value: `${target.embeds[0].description}` }] - : [], - ) - .setFooter({ text: `Server: ${getGuildName(interaction.client, messageInDb.serverId)}` }) - : EmbedBuilder.from(target.embeds[0]).setDescription(newMessage); - - const censoredEmbed = EmbedBuilder.from(newEmbed).setDescription(censoredNewMessage); - - // find all the messages through the network - const channelSettingsArr = await db.connectedList.findMany({ - where: { channelId: { in: messageInDb.channelAndMessageIds.map((c) => c.channelId) } }, - }); - - const results = messageInDb.channelAndMessageIds.map(async (element) => { - const channelSettings = channelSettingsArr.find((c) => c.channelId === element.channelId); - if (!channelSettings) return false; - - // fetch each channel the message was sent to - const channel = await interaction.client.channels.fetch(element.channelId).catch(() => null); - if (!channel?.isTextBased()) return false; - - // fetch the message from the channel and the webhook from the message - const message = await channel.messages.fetch(element.messageId).catch(() => null); - const webhook = await message?.fetchWebhook()?.catch(() => null); - - if (!webhook || webhook.owner?.id !== interaction.client.user.id) return false; - - // finally, edit the message - return await webhook - .editMessage(element.messageId, { - files: [], - threadId: channelSettings.parentId ? channelSettings.channelId : undefined, - embeds: !channelSettings.compact - ? [channelSettings.profFilter ? censoredEmbed : newEmbed] - : undefined, - }) - .then(() => true) - .catch(() => false); - }); - - const resultsArray = await Promise.all(results); - const deleted = resultsArray.reduce((acc, cur) => acc + (cur ? 1 : 0), 0); - await editInteraction - .editReply(`${emojis.normal.yes} Your message has been edited in __**${deleted}/${resultsArray.length}**__ servers.`); - - if (interaction.inCachedGuild()) networkMsgUpdate(interaction.member, target, newMessage); - }, -}; diff --git a/src/Commands/Apps/messageInfo.ts b/src/Commands/Apps/messageInfo.ts deleted file mode 100644 index e21b0fdf..00000000 --- a/src/Commands/Apps/messageInfo.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { ActionRowBuilder, ApplicationCommandType, AttachmentBuilder, ButtonBuilder, ButtonStyle, ComponentType, ContextMenuCommandBuilder, EmbedBuilder, MessageContextMenuCommandInteraction, ModalBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle, ThreadChannel } from 'discord.js'; -import { constants, getDb } from '../../Utils/utils'; -import { stripIndents } from 'common-tags'; -import { profileImage } from 'discord-arts'; -import emojis from '../../Utils/JSON/emoji.json'; -import { captureException } from '@sentry/node'; -import logger from '../../Utils/logger'; - -export default { - description: 'Get information about this message, user and server it was sent from!', - data: new ContextMenuCommandBuilder() - .setName('Message Info') - .setType(ApplicationCommandType.Message), - async execute(interaction: MessageContextMenuCommandInteraction) { - const db = getDb(); - const target = interaction.targetMessage; - const networkMessage = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: target.id } } }, - include: { hub: true }, - }); - - if (!networkMessage) { - await interaction.reply({ - content: 'Information about this message is no longer available.', - ephemeral: true, - }); - return; - } - - const author = await interaction.client.users.fetch(networkMessage.authorId); - const server = await interaction.client.guilds.fetch(networkMessage.serverId).catch(() => null); - - const embed = new EmbedBuilder() - .setThumbnail(author.displayAvatarURL()) - .setDescription(stripIndents` - ## ${emojis.normal.clipart} Message Info - - **Sent By:** - __${author.username}__${author.discriminator !== '0' ? `#${author.discriminator}` : ''} ${author.bot ? '(Bot)' : ''} - - **Sent From:** - __${server?.name}__ - - **Message ID:** - __${target.id}__ - - **Sent In (Hub):** - __${networkMessage.hub?.name}__ - - **Message Created:** - - `) - .setColor('Random'); - - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('messageInfo') - .setLabel('Message Info') - .setStyle(ButtonStyle.Secondary) - .setDisabled(true), - new ButtonBuilder() - .setCustomId('serverInfo') - .setLabel('Server Info') - .setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId('userInfo') - .setLabel('User Info') - .setStyle(ButtonStyle.Secondary), - ); - - const reportButton = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('report') - .setLabel('Report') - .setStyle(ButtonStyle.Danger), - ); - - const replyMsg = await interaction.reply({ - embeds: [embed], - components: [buttons, reportButton], - ephemeral: true, - }); - - // create a variable to store the profile card buffer - let customCard: AttachmentBuilder | null = null; - - const collector = replyMsg?.createMessageComponentCollector({ idle: 30_000 }); - collector.on('collect', async i => { - if (i.customId === 'serverInfo') { - if (!server) { - i.update({ content: 'Unable to find server!', embeds: [] }); - return; - } - - const owner = await server.fetchOwner(); - const createdAt = Math.round(server.createdTimestamp / 1000); - const guildSetup = await db.connectedList.findFirst({ where: { serverId: networkMessage.serverId } }); - - const serverEmbed = new EmbedBuilder() - .setColor(constants.colors.invisible) - .setThumbnail(server.iconURL()) - .setImage(server.bannerURL()) - .setDescription(stripIndents` - ## ${server?.name} - ${server.description || 'No Description.'} - - **Owner:** - __${owner.user.username}__${owner.user.discriminator !== '0' ? `#${owner.user.discriminator}` : ''} ${owner.user.bot ? '(Bot)' : ''} - - **Created:** - () - - **Member Count:** - __${server.memberCount}__ - - **Invite:** - __${guildSetup?.invite ? `[\`${guildSetup.invite}\`](https://discord.gg/${guildSetup.invite})` : 'Not Set.'}__`) - .setFooter({ text: `ID: ${server.id}` }); - - const components: ActionRowBuilder[] = []; - const newButtons = ActionRowBuilder.from(buttons); - newButtons.components[0].setDisabled(false); - newButtons.components[1].setDisabled(true); - components.push(newButtons); - - if (guildSetup?.invite) { - components.push( - new ActionRowBuilder().addComponents(new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setURL(`https://discord.gg/${guildSetup?.invite}`) - .setEmoji(emojis.icons.join) - .setLabel('Join')), - ); - } - - await i.update({ embeds: [serverEmbed], components, files: [] }); - } - - - else if (i.customId === 'userInfo') { - await i.deferUpdate(); - - const createdAt = Math.round(author.createdTimestamp / 1000); - - const userEmbed = new EmbedBuilder() - .setThumbnail(author.displayAvatarURL()) - .setColor('Random') - .setImage(author.bannerURL() ?? null) - .setDescription(stripIndents` - ## ${author.username}${author.discriminator !== '0' ? `#${author.discriminator}` : ''} ${author.bot ? '(Bot)' : ''} - - **ID:** - __${author.id}__ - - **Created:** - () - - **Display Name:** - __${author.globalName || 'Not Set.'}__ - - **Hubs Owned:** - __${await db.hubs.count({ where: { ownerId: author.id } })}__ - `) - .setImage('attachment://customCard.png') // link to image that will be generated afterwards - .setTimestamp(); - - // disable the user info button - const newButtons = ActionRowBuilder.from(buttons); - newButtons.components[0].setDisabled(false); - newButtons.components[2].setDisabled(true); - - // generate the profile card - if (!customCard) customCard = new AttachmentBuilder(await profileImage(author.id), { name: 'customCard.png' }); - - await i.editReply({ - embeds: [userEmbed], - files: [customCard], - components: [newButtons], - }); - } - - - else if (i.customId === 'messageInfo') { - await i.update({ embeds: [embed], components: [buttons], files: [] }); - } - - - else if (i.customId === 'report') { - if (networkMessage.authorId === i.user.id) { - i.reply({ content: 'You cannot report yourself!', ephemeral: true }); - return; - } - - const reportsChannel = await i.client.channels.fetch(constants.channel.reports) as ThreadChannel; - const reportedUser = await i.client.users.fetch(networkMessage.authorId); - - // network channelId in chatbot hq - const cbhqJumpMsg = networkMessage.channelAndMessageIds.find((x) => x.channelId === '821607665687330816'); - - const confirmEmbed = new EmbedBuilder() - .setTitle('Report Type') - .setDescription('Thank you for submitting a report. In order for our staff team to investigate, please specify the reason for your report. If you are reporting a server or bug, please use the /support report command instead.') - .setFooter({ text: 'Submitting false reports will result in a warning.' }) - .setColor(constants.colors.interchatBlue); - - const typeSelect = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('type') - .setPlaceholder('Choose a report type.') - .setMaxValues(2) - .addOptions([ - new StringSelectMenuOptionBuilder() - .setLabel('Harassment') - .setDescription('Verbal or written abuse or threats.') - .setValue('Harassment'), - new StringSelectMenuOptionBuilder() - .setLabel('Bullying') - .setDescription('Repeated aggressive behavior that is intended to harm, intimidate, or control another person.') - .setValue('Bullying'), - new StringSelectMenuOptionBuilder() - .setLabel('Toxicity') - .setDescription('Hate speech, discrimination, or offensive language.') - .setValue('Toxicity'), - new StringSelectMenuOptionBuilder() - .setLabel('Spamming') - .setDescription('Repeated unwanted messages or links in chat.') - .setValue('Spamming'), - new StringSelectMenuOptionBuilder() - .setLabel('Scamming') - .setDescription('Fraud or deceitful behavior.') - .setValue('Scamming'), - new StringSelectMenuOptionBuilder() - .setLabel('Impersonation') - .setDescription('Pretending to be someone else.') - .setValue('Impersonation'), - new StringSelectMenuOptionBuilder() - .setLabel('NSFW Content') - .setDescription('Inappropriate or offensive content.') - .setValue('NSFW'), - ]), - ); - - const message = await i.reply({ - embeds: [confirmEmbed], - components: [typeSelect], - ephemeral: true, - fetchReply: true, - }); - - const selectCollector = message.createMessageComponentCollector({ - componentType: ComponentType.StringSelect, - idle: 60_000, - max: 1, - }); - - selectCollector.on('collect', async (selInterac) => { - const selections = selInterac.values; - - const modal = new ModalBuilder() - .setCustomId(i.id) - .setTitle('Report') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setRequired(false) - .setCustomId('reason') - .setStyle(TextInputStyle.Paragraph) - .setLabel('Additional Details (OPTIONAL)') - .setMaxLength(2000), - ), - ); - - await selInterac.showModal(modal); - - selInterac.awaitModalSubmit({ time: 60_000 * 5 }) - .then(async (modalSubmit) => { - await modalSubmit.deferUpdate(); - - const reason = modalSubmit.fields.getTextInputValue('reason'); - - const reportEmbed = new EmbedBuilder() - .setTitle('User Reported') - .setDescription(`A new user report for \`@${reportedUser.username}\` (${reportedUser.id}) was submitted.\n\n**Reported For:** ${selections.join(', ')}`) - .setColor(constants.colors.interchatBlue) - .setTimestamp() - .setFooter({ - text: `Reported By: ${modalSubmit.user.username} | ${modalSubmit.user.id}.`, - iconURL: modalSubmit.user.avatarURL() || modalSubmit.user.defaultAvatarURL, - }); - - if (reason) reportEmbed.addFields({ name: 'Additional Details', value: reason }); - - const jumpButton = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('Jump') - .setURL(`https://discord.com/channels/${constants.guilds.cbhq}/${cbhqJumpMsg?.channelId}/${cbhqJumpMsg?.messageId}`) - .setStyle(ButtonStyle.Link), - ); - - await reportsChannel?.send({ - content: '<@&1088677008260726854>', // ping network mods role - embeds: [reportEmbed], - components: [jumpButton], - }); - - await i.editReply({ - content: `${emojis.normal.yes} Your report has been successfully submitted! Join the support server to check the status of your report.`, - embeds: [], - components: [], - }); - }) - .catch((e) => { - if (!e.message.includes('with reason: time')) { - logger.error(e); - captureException(e); - - i.followUp({ - content: `${emojis.normal.no} An error occored while making the report.`, - ephemeral: true, - }); - } - return null; - }); - }); - } - }); - }, -}; \ No newline at end of file diff --git a/src/Commands/Apps/translate.ts b/src/Commands/Apps/translate.ts deleted file mode 100644 index df8460f3..00000000 --- a/src/Commands/Apps/translate.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { ContextMenuCommandBuilder, ApplicationCommandType, MessageContextMenuCommandInteraction, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; -import { getDb, topgg } from '../../Utils/utils'; -import { translateText } from '../../Utils/translate'; -import { captureException } from '@sentry/node'; -import { supportedLanguages } from '@translate-tools/core/translators/GoogleTranslator'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - description: 'Translate a message that was sent in a network channel. (Vote only)', - data: new ContextMenuCommandBuilder() - .setName('Translate') - .setType(ApplicationCommandType.Message), - async execute(interaction: MessageContextMenuCommandInteraction) { - await interaction.deferReply({ ephemeral: true }); - - const hasVoted = await topgg.hasVoted(interaction.user.id); - if (!hasVoted) return await interaction.editReply('Please [vote](https://top.gg/bot/769921109209907241/vote) for Interchat to use this command, your support is very much appreciated!'); - - const target = interaction.targetMessage; - - const db = getDb(); - const messageInDb = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: target.id } } }, - }); - - if (!messageInDb) return interaction.editReply('This message has expired. If not, please wait a few seconds and try again.'); - - const messageContent = target.content || target.embeds[0]?.description; - if (!messageContent) return interaction.editReply('This message is not translatable.'); - - const translatedMessage = await translateText(messageContent, 'en', 'auto'); - const embed = new EmbedBuilder() - .setDescription('### Translation Results') - .setColor('Green') - .addFields({ - name: 'Original Message', - value: messageContent, - inline: true, - }, - { - name: 'Translated Message', - value: translatedMessage, - inline: true, - }) - .setFooter({ text: 'Translations provided may not be accurate.' }); - - - const init = await interaction.editReply({ - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('lang') - .setLabel('Specify Language') - .setStyle(ButtonStyle.Secondary) - .setEmoji('🌐'), - ), - ], - embeds: [embed], - }); - - const collector = init.createMessageComponentCollector({ - componentType: ComponentType.Button, - idle: 60000, - }); - - collector.on('collect', async (i) => { - if (i.customId !== 'lang') return; - - const modal = new ModalBuilder() - .setCustomId(i.id) - .setTitle('Specify Language') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('from') - .setLabel('From Language') - .setPlaceholder('Input Language Code (e.g. en, fr, de)') - .setStyle(TextInputStyle.Short) - .setMinLength(2) - .setMaxLength(2), - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('to') - .setLabel('To Language') - .setPlaceholder('Input Language Code (e.g. en, fr, de)') - .setStyle(TextInputStyle.Short) - .setMinLength(2) - .setMaxLength(2), - ), - ); - - await i.showModal(modal); - - const modalInter = await i.awaitModalSubmit({ filter: (e) => e.customId === i.id, time: 60_000 }) - .catch((e) => { - !e.message.includes('reason: time') ? captureException(e) : null; - return null; - }); - - if (!modalInter) return; - const to = modalInter.fields.getTextInputValue('to'); - const from = modalInter.fields.getTextInputValue('from'); - if (!supportedLanguages.includes(from) || !supportedLanguages.includes(to)) { - await modalInter.reply({ - content: `${emojis.normal.no} Invalid language code. Please use one from the [here](https://cloud.google.com/translate/docs/languages).`, - ephemeral: true, - }); - return; - } - - await modalInter.deferUpdate(); - - const newTranslation = await translateText(messageContent, to, from); - const newEmbed = EmbedBuilder.from(embed).spliceFields(1, 1, { - name: 'Translated Message', - value: newTranslation, - inline: true, - }); - - await i.editReply({ embeds: [newEmbed] }); - }); - }, -}; \ No newline at end of file diff --git a/src/Commands/Developer/logout.ts b/src/Commands/Developer/logout.ts deleted file mode 100644 index 6ffb2bc3..00000000 --- a/src/Commands/Developer/logout.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'; - -export default { - developer: true, - data: new SlashCommandBuilder() - .setName('logout') - .setDescription('Logs the bot out.') - .setDefaultMemberPermissions('0'), - async execute(interaction: ChatInputCommandInteraction) { - await interaction.reply('Logged Out!'); - interaction.client.destroy(); - process.exit(0); - }, -}; diff --git a/src/Commands/Information/help.ts b/src/Commands/Information/help.ts deleted file mode 100644 index a89cd0f1..00000000 --- a/src/Commands/Information/help.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { SlashCommandBuilder, ChatInputCommandInteraction, ActionRowBuilder, Interaction, StringSelectMenuBuilder, EmbedBuilder, APISelectMenuOption, ApplicationCommandType, ComponentType, Client, chatInputApplicationCommandMention, User, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { checkIfStaff, constants, getCredits } from '../../Utils/utils'; -import { InterchatCommand } from '../../../typings/discord'; -import { stripIndents } from 'common-tags'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - data: new SlashCommandBuilder() - .setName('help') - .setDescription('Want help? Here it comes!'), - async execute(interaction: ChatInputCommandInteraction) { - const embed = new EmbedBuilder() - .setColor(constants.colors.interchatBlue) - .setThumbnail(interaction.client.user.avatarURL()) - .setFooter({ text: `Requested by @${interaction.user.username}`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL }) - .setDescription(stripIndents` - ## InterChat Help - InterChat is a powerful discord bot that enables effortless cross-server chatting! Get started by looking at the categories below. - ### Categories: - - ${emojis.normal.slashCommand} [**All Commands**](https://discord-interchat.github.io/docs/category/commands) - - 👥 [**InterChat Hubs**](https://discord-interchat.github.io/docs/hub/joining) - - ⚙️ [**Setting up InterChat**](https://discord-interchat.github.io/docs/setup) - - 💬 [**Messaging & Network**](https://discord-interchat.github.io/docs/messaging) - `); - - const selects = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder({ - customId: 'guideSelect', - options: [ - { - label: 'Hubs', - value: 'hubs', - emoji: '👥', - description: 'How to join, leave, create, delete and use Hubs.', - }, - { - label: 'Network', - value: 'network', - emoji: '🌐', - description: 'How the InterChat network (Inter-Server Chat) works.', - }, - { - label: 'Messaging', - value: 'messaging', - emoji: '💬', - description: 'How to send, edit, delete and react to network messages!', - }, - { - label: 'Commands', - value: 'commands', - emoji: emojis.normal.slashCommand, - description: 'View all of InterChat\'s commands.', - }, - { - label: 'The Team', - value: 'credits', - emoji: emojis.icons.wand, - description: 'Learn more about the team behind InterChat!', - }, - - ], - placeholder: 'Select a Category...', - }), - ); - - const linkButtons = new ActionRowBuilder().addComponents( - new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel('Invite').setURL('https://discord.com/application-directory/769921109209907241'), - new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel('Support Server').setURL('https://discord.gg/6bhXQynAPs'), - new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel('Vote me!').setURL('https://top.gg/bot/769921109209907241/vote'), - ); - - const firstReply = await interaction.reply({ embeds: [embed], components: [selects, linkButtons] }); - const collector = firstReply.createMessageComponentCollector({ - filter: i => i.user.id === interaction.user.id, - idle: 60000, - componentType: ComponentType.StringSelect, - }); - - collector.on('collect', async (i) => { - if (i.customId !== 'guideSelect') return; - - switch (i.values[0]) { - case 'hubs': { - const hubsEmbed = EmbedBuilder.from(embed) - .setDescription(stripIndents` - ## InterChat Hubs - Think of hubs as your personal chat spaces, both creatable and joinable. They're like an exclusive room, where other servers can join to engage and chat together. - - ### Hub Guides - - ${emojis.icons.join} [**Join a Hub**](https://discord-interchat.github.io/docs/hub/joining) - - ${emojis.icons.leave} [**Leave a Hub**](https://discord-interchat.github.io/docs/hub/leaving) - - ✨ [**Create a Hub**](https://discord-interchat.github.io/docs/hub/management#creating-a-hub) - - ${emojis.icons.delete} [**Delete a Hub**](https://discord-interchat.github.io/docs/hub/management#deleting-a-hub) - - 🛡️ [**Hub Moderators (Adding, Removing)**](https://discord-interchat.github.io/docs/hub/management#managing-hub-moderators) - - 📝 [**Edit Hub**](https://discord-interchat.github.io/docs/hub/management#editing-your-hub) - - ${emojis.icons.settings} [**Hub Settings**](https://discord-interchat.github.io/docs/hub/management#hub-settings) - `, - ); - - i.update({ embeds: [hubsEmbed] }); - break; - } - case 'network': { - const networkEmbed = EmbedBuilder.from(embed) - .setDescription(stripIndents` - ## The Network - Network refers to the entire web of servers that are connected to a hub. In this area, you can send messages that will appear in other channels that have been set up on other servers. - ### Network Guides - - 🌎 [**What is the network?** (Coming Soon!)](https://discord-interchat.github.io/docs/hub/network) - - ${emojis.icons.connect} [**Using the network**](https://discord-interchat.github.io/docs/messaging#sending-messages) - - ${emojis.icons.settings} [**Network Settings (Coming soon!)**](https://discord-interchat.github.io/docs/hub/network#network-settings) - `); - - i.update({ embeds: [networkEmbed] }); - break; - } - - case 'messaging': { - const messagingEmbed = EmbedBuilder.from(embed) - .setDescription(stripIndents` - ## Messaging - Messaging refers to the ability to send messages to other servers within a hub. Find out how to send messages by visiting the guides below. You can also edit and delete messages that you have sent. - ### Messaging Guides - - 📨 [**Send Messages**](https://discord-interchat.github.io/docs/messaging#sending-messages) - - ✏️ [**Edit Messages**](https://discord-interchat.github.io/docs/messaging#editing-messages) - - ${emojis.icons.delete} [**Delete Messages**](https://discord-interchat.github.io/docs/messaging#deleting-messages) - - ${emojis.normal.reply} [**Reply to Messages**](https://discord-interchat.github.io/docs/messaging#replying-to-messages) - - 😂 [**React to Messages (Coming soon!)**](https://discord-interchat.github.io/docs/message/reacting) - - ${emojis.icons.wand} [**The InterChat Team (Coming Soon!)**](https://discord-interchat.github.io/docs/credits) - - 📑 [**Report Messages**](https://discord-interchat.github.io/docs/messaging#reporting-messages--users) - `); - - i.update({ embeds: [messagingEmbed] }); - break; - } - case 'commands': { - await this.showCommands(i); - break; - } - case 'credits': { - await i.deferUpdate(); - const { normal, icons } = emojis; - - const members: User[] = []; - const credits = await getCredits(); - for (const credit of credits) { - const member = await i.client.users.fetch(String(credit)); - members.push(member); - } - - const linksDivider = `${normal.blueLine.repeat(9)} **LINKS** ${normal.blueLine.repeat(9)}`; - const creditsDivider = `${normal.blueLine.repeat(9)} **TEAM** ${normal.blueLine.repeat(9)}`; - - const creditsEmbed = EmbedBuilder.from(embed) - .setDescription(` - ## ${icons.wand} The Team - InterChat is a project driven by a passionate team dedicated to enhancing the Discord experience. We welcome new members to join our team; if you're interested, please join our support server. - - ${creditsDivider} - ${normal.interchatCircle} **Design:** - ${normal.dotBlue} @${members[6]?.username} (Mascot) - ${normal.dotBlue} @${members[4]?.username} (Avatar) - ${normal.dotBlue} @${members[0]?.username} (Avatar) - ${normal.dotBlue} @${members[5]?.username} (Avatar & Server Icon) - - ${icons.botdev} **Developers:** - ${normal.dotBlue} @${members[1]?.username} - ${normal.dotBlue} @${members[2]?.username} - ${normal.dotBlue} @${members[0].username} - - ${icons.staff} **Staff: (Recruiting!)** - ${normal.dotBlue} @${members[4]?.username} - ${normal.dotBlue} @${members[3]?.username} - ${normal.dotBlue} @${members[5]?.username} - - ${linksDivider} - [Guide](https://discord-interchat.github.io/docs) • [Invite](https://discord.com/application-directory/769921109209907241) • [Support Server](https://discord.gg/6bhXQynAPs) • [Vote](https://top.gg/bot/769921109209907241/vote) • [Privacy](https://discord-interchat.github.io/legal/privacy) • [Terms](https://discord-interchat.github.io/legal/terms) - `); - - await i.editReply({ embeds: [creditsEmbed] }); - } - } - }); - - - }, - async showCommands(interaction: Interaction) { - if (!interaction.isRepliable()) return; - - await fetchAllCommands(interaction.client); - - const commands = interaction.client.commands; - const isStaff = checkIfStaff(interaction.user.id); - - const ignoreDirs = isStaff ? [] : ['Developer', 'Staff']; - const menuOptionsObj = commands.reduce((obj: Record, command) => { - if (!ignoreDirs.includes(command.directory) && !obj[command.directory]) { - obj[command.directory] = { label: command.directory, value: command.directory }; - } - return obj; - }, {}); - - const menuOptions = Object.values(menuOptionsObj); - menuOptions[0].default = true; - - const categorySelect = new ActionRowBuilder() - .addComponents(new StringSelectMenuBuilder({ customId: 'categorySelect', options: menuOptions, placeholder: 'Select a Category' })); - - const firstCategory = menuOptions[0].label; - let allCommands = ''; - - commands.forEach(async command => { - if (command.directory === firstCategory) { - allCommands += prettifyHelp(command, interaction.client); - } - }); - - const embed = new EmbedBuilder() - .setTitle(firstCategory + ' Commands') - .setAuthor({ name: `${interaction.client.user.username} Help`, iconURL: interaction.client.user.avatarURL() || undefined }) - .setDescription(allCommands) - .setColor(constants.colors.interchatBlue) - .setFooter({ text: `Requested by @${interaction.user.username}`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL }); - - const firstReply = await interaction.reply({ embeds: [embed], components: [categorySelect], ephemeral: true, fetchReply: true }); - - const collector = firstReply.createMessageComponentCollector({ - filter: i => i.user.id === interaction.user.id, - idle: 60000, - componentType: ComponentType.StringSelect, - }); - collector.on('collect', (i) => { - if (i.customId === 'categorySelect') { - const category = i.values[0]; - - // reset values - allCommands = ''; - commands.forEach((command) => { - if (command.directory === category) { - allCommands += prettifyHelp(command, interaction.client); - } - }); - - const categoryEmbed = new EmbedBuilder() - .setTitle(category + ' Commands') - .setAuthor({ name: `${interaction.client.user.username} Help`, iconURL: interaction.client.user.avatarURL() || undefined }) - .setDescription(allCommands) - .setColor(constants.colors.interchatBlue) - .setFooter({ text: `Requested by @${interaction.user.username}`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL }); - - categorySelect.components[0].options.find(option => option.data.default)?.setDefault(false); - categorySelect.components[0].options.find(option => option.data.value === category)?.setDefault(true); - - i.update({ embeds: [categoryEmbed], components: [categorySelect] }); - } - }); - }, -}; - -function getCommandDescription(command: InterchatCommand | undefined) { - const commandData: any = command?.data; - let description = command?.description; - let commandType: ApplicationCommandType = commandData.type; - - if (!commandData?.type) { - description = commandData.description; - commandType = ApplicationCommandType.ChatInput; - } - - return { description, commandType }; -} - -async function fetchAllCommands(client: Client) { - const rawCommands = await client.application?.commands.fetch({ cache: true }); - return rawCommands; -} - -function getCommandMention(commandName: string, client: Client) { - const command = client.application?.commands.cache.find(({ name }) => name === commandName); - return command?.type === ApplicationCommandType.ChatInput - ? chatInputApplicationCommandMention(command.name, command.id) - : commandName; -} - -function prettifyHelp(command: InterchatCommand, client: Client) { - const commandDesc = getCommandDescription(command); - const commandType = commandDesc.commandType !== ApplicationCommandType.ChatInput ? ' ' + emojis.normal.contextMenu : emojis.normal.slashCommand; - const commandMention = getCommandMention(command.data.name, client); - - return `${commandType} **${commandMention}**\n${emojis.normal.dividerEnd} ${commandDesc.description}\n`; -} diff --git a/src/Commands/Information/invite.ts b/src/Commands/Information/invite.ts deleted file mode 100644 index c914ecc0..00000000 --- a/src/Commands/Information/invite.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, PermissionFlagsBits, SlashCommandBuilder, ButtonStyle, ChatInputCommandInteraction, OAuth2Scopes } from 'discord.js'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - data: new SlashCommandBuilder() - .setName('invite') - .setDescription('Invite the bot to your server'), - async execute(interaction: ChatInputCommandInteraction) { - const { normal } = emojis; - - const InviteButtons = new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel('Normal') - .setURL(interaction.client.invite) - .setStyle(ButtonStyle.Link) - .setEmoji(normal.invite) - .setDisabled(false), - new ButtonBuilder() - .setLabel('Administrator') - .setURL(interaction.client.generateInvite({ - scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands], - permissions: PermissionFlagsBits.Administrator, - })) - .setStyle(ButtonStyle.Link) - .setEmoji(normal.discordStaff) - .setDisabled(false), - ]); - await interaction.reply({ - content: `Click the button to invite!\n\n${normal.invite} **Administrator** - For big servers with complex permission systems.\n**${normal.invite} Normal** - For normal functionality of the bot. \n\n\n__Support Server__: https://discord.gg/6bhXQynAPs`, - components: [InviteButtons], - ephemeral: true, - }); - }, -}; diff --git a/src/Commands/Information/listwarns.ts b/src/Commands/Information/listwarns.ts deleted file mode 100644 index 2aea94c3..00000000 --- a/src/Commands/Information/listwarns.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { AutocompleteInteraction, ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; -import { checkIfStaff, getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - data: new SlashCommandBuilder() - .setName('listwarns') - .setDescription('List all warnings for a user.') - .addStringOption((option) => - option - .setName('user') - .setDescription('The user to list warnings for. Use their ID if they are not in the server.') - .setRequired(false) - .setAutocomplete(true), - ), - async execute(interaction: ChatInputCommandInteraction) { - const db = getDb(); - const userInput = interaction.options.getString('user') || interaction.user.id; - - // if user is not staff the ID they provided is someone else's - const staffUser = checkIfStaff(interaction.user.id); - - if (!staffUser && userInput !== interaction.user.id) { - return interaction.reply({ - content: `${emojis.normal.no} You do not have the necessary permissions to view warnings given to other users.`, - ephemeral: true, - }); - } - - // fetch the user from discord to check if they actually exist - const resolvedUser = typeof userInput === 'string' - ? await interaction.client.users.fetch(userInput) - .catch(() => { - return interaction.client.users.cache.find((usr) => usr.tag === userInput); - }) - : interaction.user; - - // reply with an error if they don't - if (!resolvedUser) { - return interaction.reply({ - content: `${emojis.normal.no} Invalid user provided.`, - ephemeral: true, - }); - } - - // check in Db if that entry exists - const userWarns = await db.userWarns.findFirst({ where: { userId: resolvedUser.id } }); - - // reply with an error if that entry doesn't exist - if (!userWarns?.warnings) { - return interaction.reply({ - content: `${emojis.normal.yes} No warnings found!`, - ephemeral: true, - }); - } - - const warnList = userWarns.warnings.map((warn, index) => { - return { - name: `${index + 1}. ${warn.id}`, - value: stripIndents` - ${emojis.normal.dotRed}Moderator: <@${warn.moderatorId}> - ${emojis.normal.dotRed}Date: - ${emojis.normal.dotRed}Reason: ${warn.reason}`, - }; - }); - const embed = new EmbedBuilder() - .setAuthor({ name: `Warnings for ${resolvedUser.tag}`, iconURL: resolvedUser.avatarURL() || resolvedUser.defaultAvatarURL }) - .setDescription(`**Total Warnings:** ${userWarns.warnings.length}`) - .setFields(warnList) - .setColor('Random') - .setTimestamp(); - - await interaction.reply({ embeds: [embed] }); - }, - async autocomplete(interaction: AutocompleteInteraction) { - const allWarns = await getDb().userWarns.findMany(); - const choices = allWarns.map((warn) => { - return { name: warn.userTag, value: warn.userId }; - }); - - const staffUser = checkIfStaff(interaction.user.id); - - if (!staffUser) return interaction.respond([]); - - const focusedValue = interaction.options.getFocused().toLowerCase(); - const filtered = choices - .filter((choice) => - choice.name.toLowerCase().includes(focusedValue) || - choice.value.toLowerCase().includes(focusedValue), - ) - .slice(0, 25); - - interaction.respond(filtered); - }, -}; diff --git a/src/Commands/Information/rules.ts b/src/Commands/Information/rules.ts deleted file mode 100644 index a1a5a08a..00000000 --- a/src/Commands/Information/rules.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { rulesEmbed } from '../../Utils/utils'; - -export default { - data: new SlashCommandBuilder() - .setName('rules') - .setDescription('Sends rules of the bot and chat network'), - async execute(interaction: ChatInputCommandInteraction) { - await interaction.reply({ embeds: [rulesEmbed], ephemeral: true }); - }, -}; diff --git a/src/Commands/Information/stats.ts b/src/Commands/Information/stats.ts deleted file mode 100644 index 49b2685e..00000000 --- a/src/Commands/Information/stats.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; -import { toHuman, getDb, constants } from '../../Utils/utils'; -import { totalmem } from 'os'; - -export default { - data: new SlashCommandBuilder() - .setName('stats') - .setDescription('Shows the bot\'s statistics'), - async execute(interaction: ChatInputCommandInteraction) { - const { connectedList, messageData } = getDb(); - const allConnected = await connectedList?.findMany({}); - const connectionCount = allConnected.length; - const totalNetworkMessages = await messageData.count(); - - const uptime = toHuman(interaction.client.uptime); - const docsLink = 'https://discord-interchat.github.io/docs'; - const supportServer = 'https://discord.gg/6bhXQynAPs'; - - const embed = new EmbedBuilder() - .setColor(constants.colors.interchatBlue) - .setTitle(`${interaction.client.user.username} Statistics`) - .setDescription(`__Networks__: ${connectionCount} • __Network Messges__: ${totalNetworkMessages}`) - .setFooter({ text: 'Network Messages reset every 24 hours.' }) - .addFields([ - { name: 'Uptime', value: uptime, inline: true }, - { name: 'Ping:', value: `${interaction.client.ws.ping}ms`, inline: true }, - { name: 'Bot Version:', value: `v${interaction.client.version}`, inline: true }, - { name: 'Servers:', value: `${interaction.client.guilds.cache.size}`, inline: true }, - { name: 'Ram Usage:', value: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)} MB / ${Math.round(totalmem() / 1024 / 1024 / 1024)} GB`, inline: true }, - { name: 'Commands', value: `${interaction.client.commands.size}`, inline: true }, - ]); - - const linksRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('Support') - .setStyle(ButtonStyle.Link) - .setURL(supportServer), - new ButtonBuilder() - .setLabel('Guide') - .setStyle(ButtonStyle.Link) - .setURL(docsLink), - new ButtonBuilder() - .setLabel('Invite') - .setStyle(ButtonStyle.Link) - .setURL(interaction.client.invite), - ); - - await interaction.reply({ embeds: [embed], components: [linksRow] }); - }, - -}; diff --git a/src/Commands/Information/vote.ts b/src/Commands/Information/vote.ts deleted file mode 100644 index d4d7cf15..00000000 --- a/src/Commands/Information/vote.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js'; -import { constants } from '../../Utils/utils'; - -export default { - data: new SlashCommandBuilder() - .setName('vote') - .setDescription('Voting perks and vote link.'), - async execute(interaction: ChatInputCommandInteraction) { - const embed = new EmbedBuilder() - .setDescription(stripIndents` - ## 🗳️ Vote for InterChat and Enjoy Exclusive Perks - Your contribution is invaluable in elevating InterChat's position on Top.gg. Each and every vote makes a significant difference! - - As our way of expressing gratitude for your support, we are thrilled to offer you exclusive advantages. By casting your vote for InterChat, you'll unlock: - - - Edit messages within hubs - - Translating messages (and much more on the way!) - - We deeply appreciate your unwavering support. Thank you! 🙏 - `) - .setColor(constants.colors.interchatBlue); - - const button = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setLabel('Vote!') - .setEmoji('🗳️') - .setURL('https://top.gg/bot/769921109209907241/vote'), - ); - - await interaction.reply({ embeds: [embed], components: [button] }); - }, -}; diff --git a/src/Commands/Main/hub.ts b/src/Commands/Main/hub.ts deleted file mode 100644 index 219c185c..00000000 --- a/src/Commands/Main/hub.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { SlashCommandBuilder, ChatInputCommandInteraction, ChannelType, AutocompleteInteraction } from 'discord.js'; -import { getDb } from '../../Utils/utils'; - -export default { - data: new SlashCommandBuilder() - .setName('hub') - .setDescription('Interact with hubs created on InterChat!') - // .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) - .setDMPermission(false) - .addSubcommand((subcommand) => subcommand - .setName('browse') - .setDescription('🔍 Browse publicly listed hubs on InterChat!') - .addStringOption(stringOption => - stringOption - .setName('search') - .setDescription('Search for a hub by name.') - .setAutocomplete(true) - .setRequired(false), - ) - .addStringOption(stringOption => - stringOption - .setName('sort') - .setDescription('Sort the hubs by a specific category.') - .addChoices( - { name: 'Most Active', value: 'active' }, - { name: 'Most Popular', value: 'popular' }, - { name: 'Most Connections', value: 'connections' }, - { name: 'Recently Added', value: 'recent' }, - ) - .setRequired(false), - ), - ) - .addSubcommand((subcommand) => subcommand - .setName('join') - .setDescription('🎟️ Join a hub.') - .addChannelOption(channelOption => - channelOption - .setName('channel') - .addChannelTypes(ChannelType.GuildText, ChannelType.PublicThread, ChannelType.PrivateThread) - .setDescription('The channel that will be used to connect to the hub') - .setRequired(true), - ) - .addStringOption(stringOption => - stringOption - .setName('name') - .setDescription('The name of the hub (public only)') - .setAutocomplete(true) - .setRequired(false), - ) - .addStringOption(stringOption => - stringOption - .setName('invite') - .setDescription('The invite to the (private) hub') - .setRequired(false), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('leave') - .setDescription('🚪 Leave a hub.') - .addStringOption(stringOption => - stringOption - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('create') - .setDescription('✨ Create a hub.') - .addStringOption((stringOption) => - stringOption - .setName('name') - .setDescription('The hub name') - .setRequired(true), - ) - .addStringOption((attachment) => - attachment - .setName('icon') - .setDescription('Set an icon for this hub. Must be a valid i.imgur.com image link.') - .setRequired(false), - ) - .addStringOption((attachment) => - attachment - .setName('banner') - .setDescription('Set a banner for this hub. Must be a valid i.imgur.com image link.') - .setRequired(false), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('delete') - .setDescription('🗑️ Delete a hub.') - .addStringOption((stringOption) => - stringOption - .setName('hub') - .setDescription('The hub name') - .setAutocomplete(true) - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('joined') - .setDescription('👀 View all hubs this server is a part of!'), - ) - .addSubcommand((subcommand) => - subcommand - .setName('manage') - .setDescription('📝 Manage hubs that you own') - .addStringOption((stringOption) => - stringOption - .setName('name') - .setDescription('The hub name') - .setAutocomplete(true) - .setRequired(true), - ), - ) - .addSubcommandGroup((subcommandGroup) => - subcommandGroup - .setName('invite') - .setDescription('💼 Manage invites to hubs you own') - .addSubcommand((subcommand) => - subcommand - .setName('create') - .setDescription('🔗 Create a new invite code to your (private) hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub you wish to create this invite for') - .setAutocomplete(true) - .setRequired(true), - ) - .addNumberOption(numberOpt => - numberOpt - .setName('expiry') - .setDescription('The expiry of the invite link in hours. Eg. 10 (10 hours from now)') - .setMinValue(1) - .setMaxValue(48) - .setRequired(false), - ), - ) - .addSubcommand((stringOption) => - stringOption - .setName('revoke') - .setDescription('🚫 Revoke an invite code to your hub') - .addStringOption(stringOpt => - stringOpt - .setName('code') - .setDescription('The invite code') - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('list') - .setDescription('List all moderators on a hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ), - ), - ) - .addSubcommandGroup((subcommandGroup) => - subcommandGroup - .setName('moderator') - .setDescription('Manage hub moderators') - .addSubcommand((subcommand) => - subcommand - .setName('add') - .setDescription('Add a new hub moderator') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub you wish to add moderators to') - .setAutocomplete(true) - .setRequired(true), - ) - .addUserOption(stringOpt => - stringOpt - .setName('user') - .setDescription('User who will become hub moderator') - .setRequired(true), - ) - .addStringOption(stringOpt => - stringOpt - .setName('role') - .setDescription('Determines what hub permissions they have') - .addChoices( - { name: 'Network Moderator', value: 'network_mod' }, - { name: 'Hub Manager', value: 'manager' }, - ) - .setRequired(false), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription('Remove a user from moderator position in your hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub you wish to add moderators to') - .setAutocomplete(true) - .setRequired(true), - ) - .addUserOption(userOpt => - userOpt - .setName('user') - .setDescription('The user who should be removed') - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('update') - .setDescription('Update the role of a hub moderator') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ) - .addUserOption(userOpt => - userOpt - .setName('user') - .setDescription('The moderator you wish the change') - .setRequired(true), - ) - .addStringOption(stringOpt => - stringOpt - .setName('role') - .setDescription('The moderator role to update') - .setRequired(true) - .addChoices( - { name: 'Network Moderator', value: 'network_mod' }, - { name: 'Hub Manager', value: 'manager' }, - ), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('list') - .setDescription('List all moderators on a hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('connections') - .setDescription('List all connections (ie. hub members) to a hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('settings') - .setDescription('Manage hub settings') - .addStringOption(stringOption => - stringOption - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - const subcommandGroup = interaction.options.getSubcommandGroup(); - const extra = interaction.options.getString('hub'); - - (await import(`../../Scripts/hub/${subcommandGroup || subcommand}`)).default.execute(interaction, extra); - }, - async autocomplete(interaction: AutocompleteInteraction) { - const modCmds = ['manage', 'settings', 'connections', 'invite', 'moderator']; - - const subcommand = interaction.options.getSubcommand(); - const subcommandGroup = interaction.options.getSubcommandGroup(); - const focusedValue = interaction.options.getFocused(); - let hubChoices; - - - if (subcommand === 'browse' || subcommand === 'join') { - hubChoices = await getDb().hubs.findMany({ - where: { - name: { mode: 'insensitive', contains: focusedValue }, - private: false, - }, - take: 25, - }); - - } - - else if (modCmds.includes(subcommandGroup || subcommand)) { - hubChoices = await getDb().hubs.findMany({ - where: { - name: { - mode: 'insensitive', - contains: focusedValue, - }, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - take: 25, - }); - } - - else if (subcommand === 'leave') { - const networks = await getDb().connectedList.findMany({ - where: { serverId: interaction.guild?.id }, - select: { channelId: true, hub: true }, - take: 25, - }); - - const filteredNets = networks - .filter((network) => network.hub?.name.toLowerCase().includes(focusedValue.toLowerCase())) - .map(async (network) => { - const channel = await interaction.guild?.channels.fetch(network.channelId).catch(() => null); - return { name: `${network.hub?.name} | #${channel?.name || network.channelId}`, value: network.channelId }; - }); - - return await interaction.respond(await Promise.all(filteredNets)); - } - - else if (subcommand === 'delete') { - hubChoices = await getDb().hubs.findMany({ - where: { - ownerId: interaction.user.id, - name: { - mode: 'insensitive', - contains: focusedValue, - }, - }, - take: 25, - }); - } - - const filtered = hubChoices?.map((hub) => ({ name: hub.name, value: hub.name })); - filtered ? await interaction.respond(filtered) : null; - }, -}; diff --git a/src/Commands/Main/network.ts b/src/Commands/Main/network.ts deleted file mode 100644 index 465387ed..00000000 --- a/src/Commands/Main/network.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction, AutocompleteInteraction } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import displaySettings from '../../Scripts/network/displaySettings'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - data: new SlashCommandBuilder() - .setName('network') - .setDescription('Manage network connections for this server.') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) - .setDMPermission(false) - .addSubcommand((subcommand) => subcommand - .setName('manage') - .setDescription('Manage a network connection for this server.') - .addStringOption((stringOption) => - stringOption - .setName('network') - .setDescription('Select the network you wish to edit.') - .setRequired(true) - .setAutocomplete(true), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const db = getDb(); - const networkChannelId = interaction.options.getString('network', true); - const networkData = await db.connectedList.findFirst({ where: { channelId: networkChannelId }, include: { hub: true } }); - - // check if channel is in the server the command was used in - const channelInServer = await interaction.guild?.channels.fetch(networkChannelId).catch(() => null); - if (!channelInServer) { - return interaction.reply({ - content: `${emojis.normal.no} Please use this command in the server the joined/connected channel belongs to.`, - ephemeral: true, - }); - } - - displaySettings.execute(interaction, networkChannelId, networkData?.connected); - }, - async autocomplete(interaction: AutocompleteInteraction) { - const focusedValue = interaction.options.getFocused(); - - const networks = await getDb().connectedList.findMany({ - where: { serverId: interaction.guild?.id }, - select: { channelId: true, hub: true }, - take: 25, - }); - - const filtered = networks - .filter((network) => network.hub?.name.toLowerCase().includes(focusedValue.toLowerCase())) - .map(async (network) => { - const channel = await interaction.guild?.channels.fetch(network.channelId).catch(() => null); - return { name: `${network.hub?.name} | #${channel?.name || network.channelId}`, value: network.channelId }; - }); - - - interaction.respond(await Promise.all(filtered)); - }, -}; diff --git a/src/Commands/Network/blacklist.ts b/src/Commands/Network/blacklist.ts deleted file mode 100644 index 7ffd8762..00000000 --- a/src/Commands/Network/blacklist.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { AutocompleteInteraction, ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; - -export default { - data: new SlashCommandBuilder() - .setName('blacklist') - .setDescription('Blacklist a user or server from using the bot. Staff-only') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) - .addSubcommandGroup(subcommandGroup => - subcommandGroup - .setName('add') - .setDescription('Add blacklist') - .addSubcommand( - subcommand => - subcommand - .setName('user') - .setDescription('Blacklist a user from using the bot. Staff-only') - .addStringOption(hubOption => - hubOption - .setName('hub') - .setDescription('The name of the hub to blacklist the user from.') - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption(user => - user - .setName('user') - .setDescription('The user ID to blacklist. User tag also works if they are already cached.') - .setRequired(true)) - .addStringOption(string => - string - .setName('reason') - .setDescription('The reason for blacklisting the user.') - .setRequired(true)) - .addStringOption(option => option - .setName('duration') - .setDescription('The duration of the blacklist.') - .setMinLength(2)), - ) - .addSubcommand(subcommand => - subcommand - .setName('server') - .setDescription('Blacklist a server from using the bot. Staff-only') - .addStringOption(hubOption => - hubOption - .setName('hub') - .setDescription('The name of the hub to blacklist the user from.') - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption(server => - server - .setName('server') - .setDescription('The server ID to blacklist.') - .setRequired(true), - ) - .addStringOption(string => - string - .setName('reason') - .setDescription('The reason for blacklisting the server.') - .setRequired(true), - ) - .addStringOption(option => option - .setName('duration') - .setDescription('The duration of the blacklist.') - .setMinLength(2)), - ), - ) - .addSubcommandGroup(subcommandGroup => - subcommandGroup - .setName('remove') - .setDescription('Remove blacklist') - .addSubcommand( - subcommand => - subcommand - .setName('user') - .setDescription('Remove a user from the blacklist. Staff-only') - .addStringOption(hubOption => - hubOption - .setName('hub') - .setDescription('The name of the hub to blacklist the user from.') - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption(user => - user - .setName('user') - .setDescription('The user to remove from the blacklist. User tag also works.') - .setAutocomplete(true) - .setRequired(true), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('server') - .setDescription('Remove a server from the blacklist.') - .addStringOption(hubOption => - hubOption - .setName('hub') - .setDescription('The name of the hub to blacklist the user from.') - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption(server => - server - .setName('server') - .setDescription('The server to remove from the blacklist.') - .setAutocomplete(true) - .setRequired(true), - ), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('list') - .setDescription('List all blacklists.') - .addStringOption(string => - string - .setName('type') - .setDescription('The type of blacklist to list.') - .addChoices({ name: 'User', value: 'user' }, { name: 'Server', value: 'server' }) - .setRequired(true), - ) - .addStringOption(hubOption => - hubOption - .setName('hub') - .setDescription('The name of the hub to list from.') - .setAutocomplete(true) - .setRequired(true), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const hub = interaction.options.getString('hub', true); - const subcommand = interaction.options.getSubcommand(true); - - const db = getDb(); - const hubInDb = await db.hubs.findFirst({ where: { - name: hub, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - }); - - if (!hubInDb) { - return await interaction.reply({ - content: 'Unknown hub.', - ephemeral: true, - }); - } - else if (!hubInDb.moderators.find(({ userId }) => userId === interaction.user.id) && hubInDb.ownerId !== interaction.user.id) { - return await interaction.reply({ - content: 'You do not have the necessary permissions in the hub to use this command.', - ephemeral: true, - }); - } - - (await import(`../../Scripts/blacklist/${subcommand}`)).default.execute(interaction, hubInDb); - }, - - async autocomplete(interaction: AutocompleteInteraction) { - const db = getDb(); - const action = interaction.options.getSubcommand() as 'user' | 'server'; - const focusedHub = interaction.options.get('hub'); - - if (typeof focusedHub?.value !== 'string') return; - - if (focusedHub.focused) { - const hub = await db.hubs.findMany({ - where: { - name: { mode: 'insensitive', contains: focusedHub.value }, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - take: 25, - }); - - const filtered = hub.map(({ name: hubName }) => ({ name: hubName, value: hubName })); - return interaction.respond(filtered); - } - - switch (action) { - case 'user': { - const userOpt = interaction.options.get('user'); - - if (!userOpt?.focused || typeof userOpt.value !== 'string') return; - const userHubMod = await db.hubs.findFirst({ - where: { - name: focusedHub.value, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - }); - - if (!userHubMod) return interaction.respond([]); - - const filteredUsers = await db.blacklistedUsers.findMany({ - where: { - hubs: { some: { hubId: userHubMod.id } }, - OR: [ - { username: { mode: 'insensitive', contains: userOpt.value } }, - { userId: { mode: 'insensitive', contains: userOpt.value } }, - ], - }, - take: 25, - }); - - const choices = filteredUsers.map((user) => { return { name: user.username, value: user.userId }; }); - interaction.respond(choices); - break; - } - - case 'server': { - const serverOpt = interaction.options.get('server', true); - const serverHubMod = await db.hubs.findFirst({ - where: { - name: focusedHub.value, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - }); - if (!serverOpt.focused || typeof serverOpt.value !== 'string' || !serverHubMod) return; - - - const allServers = await db.blacklistedServers.findMany({ - where: { - hubs: { some: { hubId: serverHubMod.id } }, - OR: [ - { serverName: { mode: 'insensitive', contains: serverOpt.value } }, - { serverId: { mode: 'insensitive', contains: serverOpt.value } }, - ], - }, - take: 25, - }); - const choices = allServers.map(({ serverName, serverId }) => { return { name: serverName, value: serverId }; }); - await interaction.respond(choices); - break; - } - } - }, -}; diff --git a/src/Commands/Staff/badge.ts b/src/Commands/Staff/badge.ts deleted file mode 100644 index e052643a..00000000 --- a/src/Commands/Staff/badge.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; -import list from '../../Scripts/badge/list'; - -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('badge') - .setDescription('Manage the badges for a user. Staff-only.') - .setDefaultMemberPermissions('0') - .addSubcommand(subcommand => - subcommand - .setName('add') - .setDescription('Add a badge to a user') - .addUserOption(userOption => - userOption - .setName('user') - .setRequired(true) - .setDescription('The user to whom the badge should be added to'), - ) - .addStringOption(stringOption => - stringOption - .setName('badge') - .setRequired(true) - .setDescription('The badge to add') - .addChoices( - { name: 'Developer', value: 'Developer' }, - { name: 'Staff', value: 'Staff' }, - { name: 'Voter', value: 'Voter' }, - { name: 'Christmas2022', value: 'Christmas2022' }, - ), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('remove') - .setDescription('Remove a badge from a user') - .addUserOption(userOption => - userOption - .setName('user') - .setDescription('The user from whom the badge should be removed from') - .setRequired(true), - ) - .addStringOption(stringOption => - stringOption - .setName('badge') - .setDescription('The badge to remove') - .setRequired(true) - .addChoices( - { name: 'Developer', value: 'Developer' }, - { name: 'Staff', value: 'Staff' }, - { name: 'Voter', value: 'Voter' }, - { name: 'Christmas2022', value: 'Christmas2022' }, - ), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('list') - .setDescription('List all badges for a user') - .addUserOption(userOption => - userOption - .setName('user') - .setDescription('The user to list badges for') - .setRequired(true), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - const user = interaction.options.getUser('user', true); - const badge = interaction.options.getString('badge'); - - if (subcommand === 'list') { - list.execute(interaction, user); - } - else { - (await import(`../../Scripts/badge/${subcommand}`)).default.execute(interaction, user, badge); - } - }, -}; diff --git a/src/Commands/Staff/find.ts b/src/Commands/Staff/find.ts deleted file mode 100644 index a7710d35..00000000 --- a/src/Commands/Staff/find.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { AutocompleteInteraction, ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('find') - .setDescription('Find users/servers by name or ID.') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) - .addStringOption((option) => - option - .setName('type') - .setDescription('Specify if you want to get data on a user or guild.') - .setRequired(true) - .addChoices({ name: 'Server', value: 'server' }, { name: 'User', value: 'user' }), - ) - .addStringOption((option) => - option - .setRequired(true) - .setName('name') - .setDescription('The name or ID of your target.') - .setAutocomplete(true), - ) - .addBooleanOption((option) => - option - .setName('hidden') - .setDescription('Whether or not to make the reply visible only to you. Defaults to true.'), - ), - async execute(interaction: ChatInputCommandInteraction) { - const targetId = interaction.options.getString('name'); - const type = interaction.options.getString('type'); - const hidden = interaction.options.getBoolean('hidden') ?? true; - - (await import(`../../Scripts/find/${type}`)).default.execute(interaction, targetId, hidden); - }, - - async autocomplete(interaction: AutocompleteInteraction) { - const type = interaction.options.getString('type'); - switch (type) { - case 'server': { - const guilds = interaction.client.guilds.cache; - const focusedValue = interaction.options.getFocused().toLowerCase(); - const choices: {name: string, id: string}[] = []; - - guilds.map((guild) => choices.push({ name: guild.name, id: guild.id })); - const filtered = choices - .filter((choice) => choice.name.toLowerCase().includes(focusedValue) || choice.id.toLowerCase().includes(focusedValue)) - .slice(0, 25) - .map((choice) => ({ name: choice.name, value: choice.id })); - - interaction.respond(filtered); - break; - } - - case 'user': { - const users = interaction.client.users.cache; - const focusedValue = interaction.options.getFocused().toLowerCase(); - const choices: {username: string, id: string}[] = []; - - users.map((user) => choices.push({ username: user.username, id: user.id })); - const filtered = choices - .filter((choice) => choice.username.toLowerCase().includes(focusedValue) || choice.id.toLowerCase().includes(focusedValue)) - .slice(0, 25) - .map((choice) => ({ name: choice.username, value: choice.id })); - - interaction.respond(filtered); - break; - } - default: - break; - } - }, -}; diff --git a/src/Commands/Staff/purge.ts b/src/Commands/Staff/purge.ts deleted file mode 100644 index 9efd80d3..00000000 --- a/src/Commands/Staff/purge.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { captureException } from '@sentry/node'; -import { ChannelType, ChatInputCommandInteraction, EmbedBuilder, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; -import { getDb, toHuman } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; -import { messageData as messageDataCol } from '@prisma/client'; -import { stripIndents } from 'common-tags'; - -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('purge') - .setDescription('Mass delete network messages. Staff-only') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) - .addSubcommand(subcommand => - subcommand - .setName('server') - .setDescription('Purge network messages sent from a particular server.') - .addStringOption((opt) => - opt - .setName('server') - .setDescription('The ID of the server.') - .setRequired(true), - ) - .addIntegerOption((opt) => - opt - .setName('limit') - .setDescription('Number of messages to delete. Max: 100') - .setMaxValue(100) - .setRequired(true), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('user') - .setDescription('Purge network messages sent by a particular user. Staff-only') - .addStringOption(stringOption => - stringOption - .setName('user') - .setDescription('The ID of the user.') - .setRequired(true), - ) - .addIntegerOption((opt) => - opt - .setName('limit') - .setDescription('Number of messages to delete. Max: 100') - .setMaxValue(100) - .setRequired(true), - ), - ) - .addSubcommand( - subcommand => - subcommand - .setName('replies') - .setDescription('Purge messages from the network. Staff-only') - .addStringOption(stringOption => - stringOption - .setName('replies-to') - .setDescription('Provide the message ID to delete its replies.') - .setRequired(true), - ) - .addIntegerOption((opt) => - opt - .setName('limit') - .setDescription('Number of messages to delete. Max: 100') - .setMaxValue(100) - .setRequired(false), - ), - ) - .addSubcommand( - subcommand => - subcommand - .setName('any') - .setDescription('Purge messages from the network. Staff-only') - .addIntegerOption((opt) => - opt - .setName('limit') - .setDescription('Number of messages to delete. Max: 100') - .setMaxValue(100) - .setRequired(true), - ), - ) - .addSubcommand( - subcommand => - subcommand - .setName('after') - .setDescription('Purge messages after a certain message. Staff-only') - .addStringOption(stringOption => - stringOption - .setName('message') - .setDescription('The ID of the starting message.') - .setRequired(true), - ) - .addIntegerOption((intOption) => - intOption - .setName('limit') - .setDescription('Number of messages to delete. Max: 100, Default: 10') - .setMaxValue(100) - .setRequired(false), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - const limit = interaction.options.getInteger('limit') || 100; - const emoji = emojis; - const { messageData, connectedList } = getDb(); - const channelInHub = await connectedList.findFirst({ where: { channelId: interaction.channelId, connected: true } }); - - if (!channelInHub) { - return await interaction.reply({ - content: 'This channel is not connected to a hub.', - ephemeral: true, - }); - } - - let messagesInDb: messageDataCol[] = []; - - switch (subcommand) { - case 'server': { - const serverId = interaction.options.getString('server', true); - messagesInDb = await messageData.findMany({ - where: { serverId, hubId: channelInHub.hubId }, - orderBy: { id: 'desc' }, - take: limit, - }); - break; - } - - case 'user': { - const authorId = interaction.options.getString('user', true); - messagesInDb = await messageData.findMany({ - where: { authorId, hubId: channelInHub.hubId }, - orderBy: { id: 'desc' }, - take: limit, - }); - break; - } - case 'after': { - const messageId = interaction.options.getString('message', true); - const fetchedMsg = await interaction.channel?.messages.fetch(messageId).catch(() => null); - if (fetchedMsg) { - messagesInDb = await messageData.findMany({ - take: limit, - where: { - timestamp: { gt: fetchedMsg.createdAt }, - }, - }); - } - break; - } - - case 'replies': { - const messageId = interaction.options.getString('replies-to', true); - messagesInDb = await messageData.findMany({ - where: { hubId: channelInHub.hubId, reference: { is: { messageId } } }, - take: limit, - }); - break; - } - - - case 'any': - messagesInDb = await messageData.findMany({ - where: { hubId: channelInHub.hubId }, - orderBy: { id: 'desc' }, - take: limit, - }); - break; - - default: - break; - } - - - if (!messagesInDb || messagesInDb.length < 1) { - return await interaction.reply({ - content: 'Unable to locate messages to purge. Maybe they have expired?', - ephemeral: true, - }); - } - await interaction.deferReply({ fetchReply: true }); - - const startTime = performance.now(); - const allNetworks = await connectedList.findMany({ where: { hubId: channelInHub.hubId, connected: true } }); - const promiseResults = allNetworks.map(async network => { - try { - const channel = await interaction.client.channels.fetch(network.channelId); - - if (channel?.type === ChannelType.GuildText) { - const messageIds = messagesInDb.flatMap((dbMsg) => - dbMsg.channelAndMessageIds - .filter(({ channelId }) => channelId === channel.id) - .map(({ messageId }) => messageId), - ); - - if (messageIds.length < 1) return []; - - await channel.bulkDelete(messageIds); - return messageIds; - } - } - catch (e) { - captureException(e); - } - - return []; - }); - - const results = await Promise.all(promiseResults); - const deletedMessages = results.reduce((acc, cur) => acc + cur.length, 0); - const failedMessages = results.reduce((acc, cur) => acc + cur.length > 0 ? 0 : 1, 0); - const messages = results.filter((i) => i.length > 0).flat(); - - const resultEmbed = new EmbedBuilder() - .setDescription(stripIndents` - ### ${emoji.icons.delete} Purge Results - - Finished purging from **${allNetworks.length}** networks in \`${toHuman(performance.now() - startTime)}\`. - `) - .addFields([ - { name: 'Total Purged', value: `\`\`\`js\n${deletedMessages}\`\`\``, inline: true }, - { name: 'Errored Purges', value: `\`\`\`js\n${failedMessages}\`\`\``, inline: true }, - { name: 'Purge Limit', value: `\`\`\`js\n${limit || 'None'}\`\`\``, inline: true }, - ]) - .setFooter({ text: `Purged By: ${interaction.user.username}`, iconURL: interaction.user.avatarURL() || undefined }) - .setTimestamp() - .setColor('Green'); - - await interaction.followUp({ embeds: [resultEmbed] }).catch(captureException); - - await messageData.deleteMany({ - where: { channelAndMessageIds: { some: { messageId: { in: messages } } } }, - }).catch(captureException); - }, -}; diff --git a/src/Commands/Staff/server.ts b/src/Commands/Staff/server.ts deleted file mode 100644 index b0cc3ec0..00000000 --- a/src/Commands/Staff/server.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js'; - -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('server') - .setDescription('Leaves the specified server. Staff-only.') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) - .addSubcommand((subcommand) => - subcommand - .setName('leave') - .setDescription('Leaves the specified server. Staff-only.') - .addStringOption((stringOption) => - stringOption - .setName('server') - .setDescription('The server to leave.') - .setRequired(true), - ) - .addStringOption((stringOption) => - stringOption - .setName('reason') - .setDescription('The reason for leaving the server.') - .setRequired(true), - ) - .addBooleanOption(boolOption => - boolOption - .setName('notify') - .setDescription('Whether or not to notify the server about the leave. (Default: true)') - .setRequired(false), - ), - ), - async execute(interaction: ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - - await (await import(`../../Scripts/server/${subcommand}`)).default.execute(interaction); - }, -}; \ No newline at end of file diff --git a/src/Commands/Staff/suggestions.ts b/src/Commands/Staff/suggestions.ts deleted file mode 100644 index 39d7b13f..00000000 --- a/src/Commands/Staff/suggestions.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; - -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('suggestion') - .setDescription('Actions for the support team to interact with suggestions.') - .addSubcommand(subcommand => - subcommand - .setName('update') - .setDescription('Set a status for suggestions. Eg. Pending, Approved, Implemented') - .addStringOption(messageOption => - messageOption - .setName('postid') - .setDescription('The ID of the suggestion message.') - .setRequired(true), - ) - .addStringOption(statusOption => - statusOption - .setName('status') - .setDescription('The status to set to the suggestion') - .setRequired(true) - .addChoices( - { name: 'Approved', value: '✅ Approved' }, - { name: 'Pending', value: '🧑‍💻 Pending' }, - { name: 'Implemented', value: '✅ Implemented' }, - { name: 'Rejected', value: '❌ Rejected' }, - { name: 'Closed', value: '🚫 Closed' }, - ), - ) - .addStringOption(reasonOption => - reasonOption - .setName('reason') - .setDescription('An optional message to explain to the suggestion author about the change') - .setRequired(false), - ), - ).addSubcommand(subcommand => - subcommand - .setName('takedown') - .setDescription('Delete the suggestion message. This is irreversible!') - .addStringOption(messageOption => - messageOption - .setName('postid') - .setDescription('The ID of the suggestion message.') - .setRequired(true), - ) - .addBooleanOption(booloption => - booloption - .setName('keepmessage') - .setDescription('If set to true the suggestion message will be edited to leave a note saying it was taken down.') - .setRequired(true)), - ), - execute: async (interaction: ChatInputCommandInteraction) => { - const subcommand = interaction.options.getSubcommand(); - const script = await import(`../../Scripts/suggestions/${subcommand}`); - script.default.execute(interaction); - }, -}; \ No newline at end of file diff --git a/src/Commands/Staff/warn.ts b/src/Commands/Staff/warn.ts deleted file mode 100644 index 677c6337..00000000 --- a/src/Commands/Staff/warn.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js'; - -export default { - staff: true, - data: new SlashCommandBuilder() - .setName('warn') - .setDescription('Warn a user. Staff-only') - .addSubcommand((subcommand) => - subcommand - .setName('add') - .setDescription('Warn a user') - .addUserOption((option) => - option - .setName('user') - .setDescription('The user to warn. Use their ID if they are not in the server.') - .setRequired(true), - ) - .addStringOption((option) => - option.setName('reason').setDescription('The reason for the warning.').setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription('Remove a warning from a user') - .addUserOption((option) => - option - .setName('user') - .setDescription('The user to remove a warning from. Use their ID if they are not in the server.') - .setRequired(true), - ) - .addStringOption((option) => - option - .setName('id') - .setDescription('The warning id. Use /listwarns to see the list of ids.') - .setRequired(true), - ) - .addStringOption((option) => - option - .setName('reason') - .setDescription('The reason for removing the warning.') - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('clear') - .setDescription('Clear all warnings for a user') - .addUserOption((option) => - option - .setName('user') - .setDescription( - 'The user to clear warnings for. Use their ID if they are not in the server.', - ) - .setRequired(true), - ) - .addStringOption((option) => - option - .setName('reason') - .setDescription('The reason for clearing the warnings.') - .setRequired(true), - ), - ) - .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), - async execute(interaction: ChatInputCommandInteraction) { - // const subcommand = interaction.options.getSubcommand(); - // require('../../Scripts/warn/' + subcommand).execute(interaction); - // disabled until strike system is implemented - return interaction.reply('This command is currently disabled.'); - }, -}; diff --git a/src/Commands/Support/support.ts b/src/Commands/Support/support.ts deleted file mode 100644 index c0284e92..00000000 --- a/src/Commands/Support/support.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; - -export default { - data: new SlashCommandBuilder() - .setName('support') - .setDescription('Send the developers suggestions/reports.') - .setDMPermission(false) - .addSubcommand(subcommand => - subcommand - .setName('suggest') - .setDescription('Suggest commands/features to be added to the bot.') - .addAttachmentOption(option => - option - .setName('screenshot') - .setDescription('Attach a screenshot of your suggestion. (Optional)') - .setRequired(false), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('report') - .setDescription('Report a user, server, bug, or others in the bot.') - .addStringOption(option => - option - .setName('type') - .setRequired(true) - .setDescription('The type of report.') - .addChoices( - { name: 'User', value: 'user' }, - { name: 'Server', value: 'server' }, - { name: 'Bug', value: 'bug' }, - { name: 'Other', value: 'other' }), - ), - ) - .addSubcommand(subcommand => - subcommand - .setName('server') - .setDescription('Get the invite to the support server.'), - ), - async execute(interaction: ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - (await import(`../../Scripts/support/${subcommand}`)).default.execute(interaction); - }, -}; diff --git a/src/Events/guildCreate.ts b/src/Events/guildCreate.ts deleted file mode 100644 index 55ca49f2..00000000 --- a/src/Events/guildCreate.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { EmbedBuilder, AuditLogEvent, Guild, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js'; -import { sendInFirst, constants } from '../Utils/utils'; -import { stripIndents } from 'common-tags'; -import { captureException } from '@sentry/node'; -import wordFilter from '../Utils/wordFilter'; -import emojis from '../Utils/JSON/emoji.json'; - -export default { - name: 'guildCreate', - async execute(guild: Guild) { - const { normal, mascot } = emojis; - - const embed = new EmbedBuilder() - .setTitle(`Thank you for inviting me! ${normal.tada}`) - .setDescription(stripIndents` - With ${guild.client.user.username}, you can talk to different servers from your own. Find lots of public hubs and connect to multiple servers in that hub! A fun inter-server chat that is sure enhance your server's experience! ${normal.clipart}! - - - Use to find a hub of your liking and to join it! - - Use to create your own hub and invite your friends! - - Please follow the network while using the network at all times. - - Unlock cool new features by voting on [top.gg](https://top.gg/bot/769921109209907241/vote)! - - If you want learn more about me, you can do so by reading the [guide](https://discord-interchat.github.io/docs/). - - We hope you enjoy using ${guild.client.user.username}! If you have any questions or feedback, please don't hesitate to reach out to us in the [official support server](https://discord.gg/6bhXQynAPs). - `) - .setColor(constants.colors.interchatBlue) - .setFooter({ text: `Sent for ${guild.name}`, iconURL: guild.iconURL() || undefined }); - - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('Guide') - .setURL('https://discord-interchat.github.io/docs/setup') - .setStyle(ButtonStyle.Link), - new ButtonBuilder() - .setLabel('ToS') - .setURL('https://discord-interchat.github.io/docs/legal/terms') - .setStyle(ButtonStyle.Link), - new ButtonBuilder() - .setLabel('Privacy') - .setURL('https://discord-interchat.github.io/docs/legal/privacy') - .setStyle(ButtonStyle.Link), - ); - - - const badword = wordFilter.check(guild.name); - if (badword) { - await sendInFirst( - guild, - 'The server name contains one or more bad words. Please change the name and try inviting me again.', - ); - await guild.leave(); - return; - } - - let inviter; - const auditLogs = await guild - .fetchAuditLogs({ type: AuditLogEvent.BotAdd, limit: 5 }) - .catch(() => null); - - if (auditLogs) { - const inviteLog = auditLogs.entries.find((bot) => bot.target?.id === guild.client.user?.id); - inviter = inviteLog?.executor; - - await inviter?.send({ embeds: [embed], components: [buttons] }) - .catch(() => sendInFirst(guild, { embeds: [embed], components: [buttons] }).catch(() => null)); - } - else { - await sendInFirst(guild, { embeds: [embed], components: [buttons] }).catch(() => null); - } - - const goalChannel = guild.client.channels.cache.get(constants.channel.goal); - if (!goalChannel?.isTextBased()) return; - - goalChannel.send({ - content: `${mascot.flushed} I have joined ${guild.name}! I am now in **${guild.client.guilds.cache.size}** servers! ${emojis.normal.tada}`, - embeds: [ - new EmbedBuilder() - .setColor(constants.colors.invisible) - .setAuthor({ - name: `${guild.name} ${inviter ? `• @${inviter.username}` : ''}`, - iconURL: guild.iconURL() || undefined, - }), - ], - }).catch(captureException); - }, -}; diff --git a/src/Events/guildDelete.ts b/src/Events/guildDelete.ts deleted file mode 100644 index c12e0b0f..00000000 --- a/src/Events/guildDelete.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { constants, getDb } from '../Utils/utils'; -import { EmbedBuilder, Guild, TextChannel } from 'discord.js'; -import { captureException } from '@sentry/node'; -import { stripIndents } from 'common-tags'; - -export default { - name: 'guildDelete', - async execute(guild: Guild) { - if (!guild.available) return; - const db = getDb(); - await db.connectedList.deleteMany({ where: { serverId: guild.id } }); - - const goalChannel = guild.client.channels.cache.get(constants.channel.goal) as TextChannel | undefined; - - goalChannel?.send({ - embeds: [ - new EmbedBuilder() - .setTitle('I have been kicked from a server 😢') - .setDescription(stripIndents` - I am now in **${guild.client.guilds.cache.size}** servers again! 💪 - - **Server Name:** ${guild.name} (${guild.id}) - **Member Count:** ${guild.memberCount} - `) - .setThumbnail(guild.iconURL()) - .setTimestamp() - .setColor('Random'), - ], - }).catch(captureException); - }, -}; diff --git a/src/Events/interactionCreate.ts b/src/Events/interactionCreate.ts deleted file mode 100644 index 1fd2e846..00000000 --- a/src/Events/interactionCreate.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Interaction } from 'discord.js'; -import { checkIfStaff } from '../Utils/utils'; -import { captureException } from '@sentry/node'; -import logger from '../Utils/logger'; -import reactionButton from '../Scripts/reactions/reactionButton'; -import viewReactionsMenu from '../Scripts/reactions/viewReactionsMenu'; -import { formatErrorCode } from '../Utils/errorHandler'; - -export default { - name: 'interactionCreate', - async execute(interaction: Interaction) { - if (interaction.isButton()) { - const customId = interaction.customId; - if (customId.startsWith('reaction_')) { - const cooldown = interaction.client.reactionCooldowns.get(interaction.user.id); - if (cooldown && cooldown > Date.now()) { - interaction.reply({ - content: `A little quick there! You can react again !`, - ephemeral: true, - }); - return; - } - - interaction.client.reactionCooldowns.set(interaction.user.id, Date.now() + 3000); - reactionButton.execute(interaction); - } - else if (customId === 'view_all_reactions') {viewReactionsMenu.execute(interaction);} - } - - else if (interaction.isAutocomplete()) { - const command = interaction.client.commands.get(interaction.commandName); - if (command?.autocomplete) command.autocomplete(interaction); - } - - else if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { - const command = interaction.client.commands.get(interaction.commandName); - if (!command) return; - - if (command.cooldown) { - const cooldown = interaction.client.commandCooldowns.get(`${interaction.commandName}-${interaction.user.id}`); - if (cooldown && cooldown > Date.now()) { - return interaction.reply({ - content: `You can use this command again `, - ephemeral: true, - }); - } - interaction.client.commandCooldowns.set( - `${interaction.commandName}-${interaction.user.id}`, Date.now() + command.cooldown, - ); - } - - // Check if the user is staff/developer - if (command.staff || command.developer) { - const permCheck = checkIfStaff(interaction.user.id, command.developer); - if (!permCheck) { - return interaction.reply({ - content: 'You do not have the right permissions to use this command!', - ephemeral: true, - }); - } - } - command.execute(interaction) - .catch((e) => { - logger.error(e); - captureException(e, { extra: { command: interaction.commandName } }); - - (interaction.replied || interaction.deferred - ? interaction.followUp({ content: formatErrorCode(e), ephemeral: true }) - : interaction.reply({ content: formatErrorCode(e), ephemeral: true })).catch(() => null); - }); - } - }, -}; diff --git a/src/Events/messageCreate.ts b/src/Events/messageCreate.ts deleted file mode 100644 index 3bc92d67..00000000 --- a/src/Events/messageCreate.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { - APIMessage, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - EmbedBuilder, - HexColorString, - Message, - User, - WebhookClient, - WebhookMessageCreateOptions, -} from 'discord.js'; -import { getDb } from '../Utils/utils'; -import { censor } from '../Utils/wordFilter'; -import { messageData } from '@prisma/client'; -import { HubSettingsBitField } from '../Utils/hubSettingsBitfield'; -import checks from '../Scripts/message/checks'; -import cleanup from '../Scripts/message/cleanup'; -import messageContentModifiers from '../Scripts/message/messageContentModifiers'; -import emojis from '../Utils/JSON/emoji.json'; - - -export interface NetworkMessage extends Message { - censored_content: string, -} - -export interface NetworkWebhookSendResult { - messageOrError: APIMessage | string - webhookURL: string; -} - -export default { - name: 'messageCreate', - async execute(message: NetworkMessage) { - if (message.author.bot || message.webhookId || message.system) return; - - const db = getDb(); - const connection = await db.connectedList.findFirst({ - where: { channelId: message.channel.id, connected: true }, - include: { hub: { include: { connections: { where: { connected: true } } } } }, - }); - - if (connection?.hub) { - const settings = new HubSettingsBitField(connection.hub?.settings); - if (!await checks.execute(message, connection, settings)) return; - - message.censored_content = censor(message.content); - const attachment = message.attachments.first(); - const attachmentURL = attachment - ? `attachment://${attachment.name}` - : await messageContentModifiers.getAttachmentURL(message); - - let replyInDb: messageData | null; - let referred: { author?: User, censored: string, content: string } | undefined; // for compact messages - - if (message.reference) { - const referredMessage = await message.fetchReference().catch(() => null); - if (referredMessage?.webhookId) { - replyInDb = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: referredMessage.id } } }, - }); - - const content = messageContentModifiers.getReferredContent(referredMessage); - referred = { - censored: censor(content), - content: content, - author: replyInDb - ? await message.client.users.fetch(replyInDb?.authorId).catch(() => undefined) - : undefined, - }; - - } - } - - const useNicknameSetting = settings.has('UseNicknames'); - - // for nicknames setting - const displayNameOrUsername = useNicknameSetting - ? message.member?.displayName || message.author.displayName - : message.author.username; - const avatarURL = useNicknameSetting - ? message.member?.user.displayAvatarURL() - : message.author.displayAvatarURL(); - - const embed = new EmbedBuilder() - .setDescription(message.content || null) // description must be null if message is only an attachment - .setImage(attachmentURL) - .setColor((connection.embedColor as HexColorString) || 'Random') - .setFields( - referred - ? [{ name: 'Reply to:', value: `> ${referred.content.replaceAll('\n', '\n> ')}` }] - : [], - ) - .setAuthor({ - name: displayNameOrUsername, - iconURL: avatarURL, - url: `https://discord.com/users/${message.author.id}`, - }) - .setFooter({ - text: `Server: ${message.guild?.name}`, - iconURL: message.guild?.iconURL() || undefined, - }); - - // profanity censored embed - const censoredEmbed = EmbedBuilder.from(embed) - .setDescription(message.censored_content || null) - .setFields( - referred - ? [{ name: 'Reply to:', value: `> ${referred.censored.replaceAll('\n', '\n> ')}` }] - : [], - ); - - // send the message to all connected channels in apropriate format (compact/profanity filter) - const messageResults = connection.hub?.connections?.map(async (connected) => { - const reply = replyInDb?.channelAndMessageIds.find((msg) => msg.channelId === connected.channelId); - const replyLink = reply ? `https://discord.com/channels/${connected.serverId}/${reply.channelId}/${reply.messageId}` : undefined; - const replyButton = replyLink && referred?.author - ? new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setEmoji(emojis.normal.reply) - .setURL(replyLink) - .setLabel( - (referred.author.username.length >= 80 - ? '@' + referred.author.username.slice(0, 76) + '...' - : '@' + referred.author.username), - )) - : null; - - let webhookMessage: WebhookMessageCreateOptions; - - if (connected.compact) { - const referredContent = connected.profFilter ? referred?.censored : referred?.content; - const replyEmbed = replyLink && referredContent - ? new EmbedBuilder() - .setColor('Random') - .setDescription(`[**Reply to:**](${replyLink}) ${referredContent.length >= 80 ? referredContent.slice(0, 80) + '...' : referredContent}`) - .setAuthor({ - name: `${referred?.author?.username}`, - iconURL: referred?.author?.avatarURL() || undefined, - }) - : undefined; - - webhookMessage = { - avatarURL: avatarURL, - username: displayNameOrUsername, - files: attachment ? [attachment] : undefined, - content: connected?.profFilter ? message.censored_content : message.content, - embeds: replyEmbed ? [replyEmbed] : undefined, - threadId: connected.parentId ? connected.channelId : undefined, - allowedMentions: { parse: [] }, - }; - } - else { - webhookMessage = { - components: replyButton ? [replyButton] : undefined, - embeds: [connected.profFilter ? censoredEmbed : embed], - files: attachment ? [attachment] : undefined, - username: `${connection.hub?.name}`, - avatarURL: connection.hub?.iconUrl, - threadId: connected.parentId ? connected.channelId : undefined, - allowedMentions: { parse: [] }, - }; - } - - const webhook = new WebhookClient({ url: connected.webhookURL }); - const webhookSendRes = await webhook.send(webhookMessage).catch((e) => e.message); - return { webhookURL: webhook.url, messageOrError: webhookSendRes } as NetworkWebhookSendResult; - }); - - message.delete().catch(() => null); - cleanup.execute(message, await Promise.all(messageResults), connection.hubId); - } - }, -}; - diff --git a/src/Events/messageReactionAdd.ts b/src/Events/messageReactionAdd.ts deleted file mode 100644 index 911fec45..00000000 --- a/src/Events/messageReactionAdd.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { MessageReaction, PartialMessageReaction, PartialUser, User } from 'discord.js'; -import { getDb } from '../Utils/utils'; -import updateMessageReactions from '../Scripts/reactions/updateMessage'; -import { HubSettingsBitField } from '../Utils/hubSettingsBitfield'; -import { fetchServerBlacklist, fetchUserBlacklist } from '../Utils/blacklist'; - -export default { - name: 'messageReactionAdd', - async execute(reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser) { - if (user.bot || user.system) return; - - const db = getDb(); - const messageInDb = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: reaction.message.id } } }, - include: { hub: { select: { settings: true } } }, - }); - - if ( - !messageInDb?.hub || - !messageInDb?.hubId || - !new HubSettingsBitField(messageInDb.hub?.settings).has('Reactions') || - !reaction.message.inGuild() - ) return; - - const userBlacklisted = await fetchUserBlacklist(messageInDb.hubId, user.id); - const serverBlacklisted = await fetchServerBlacklist(messageInDb.hubId, reaction.message.guild.id); - if (userBlacklisted || serverBlacklisted) return; - - const cooldown = reaction.client.reactionCooldowns.get(user.id); - if (cooldown && cooldown > Date.now()) return; - reaction.client.reactionCooldowns.set(user.id, Date.now() + 5000); - - const connections = await db.connectedList.findMany({ - where: { - channelId: { in: messageInDb?.channelAndMessageIds.map((c) => c.channelId) }, - connected: true, - }, - }); - - const reactedEmoji = reaction.emoji.toString(); - const reactions = messageInDb.reactions?.valueOf() as Record; // eg. { '👍': 1, '👎': 2 } - - if ( - (!reactions[reactedEmoji] && Object.keys(reactions).length >= 10) || - reactions[reactedEmoji]?.includes(user.id) - ) return; - - reactions[reactedEmoji] - ? reactions[reactedEmoji].push(user.id) - : (reactions[reactedEmoji] = [user.id]); - - - await db.messageData.update({ - where: { id: messageInDb.id }, - data: { reactions: reactions }, - }); - - reaction.users.remove(user.id).catch(() => null); - updateMessageReactions.execute(connections, messageInDb.channelAndMessageIds, reactions); - }, -}; diff --git a/src/Events/ready.ts b/src/Events/ready.ts deleted file mode 100644 index e86cffc8..00000000 --- a/src/Events/ready.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Client, OAuth2Scopes, PermissionFlagsBits } from 'discord.js'; -import { topgg } from '../Utils/utils'; -import startTimers from '../Utils/timers'; -import logger from '../Utils/logger'; - -export default { - name: 'ready', - once: true, - async execute(client: Client) { - logger.info(`Logged in as ${client.user?.username}! Cached ${client.guilds.cache.size} guilds.`); - - // Run misc tasks on startup - const permissions = [ - PermissionFlagsBits.ManageWebhooks, - PermissionFlagsBits.ChangeNickname, - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.SendMessagesInThreads, - PermissionFlagsBits.ManageMessages, - PermissionFlagsBits.ManageThreads, - PermissionFlagsBits.EmbedLinks, - PermissionFlagsBits.AttachFiles, - PermissionFlagsBits.ReadMessageHistory, - PermissionFlagsBits.UseExternalEmojis, - PermissionFlagsBits.AddReactions, - PermissionFlagsBits.ViewAuditLog, - ]; - client.invite = client.generateInvite({ scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands], permissions }); - topgg.postStats({ serverCount: client.guilds.cache.size }); - startTimers(client); - }, -}; diff --git a/src/Factory.ts b/src/Factory.ts new file mode 100644 index 00000000..b2858d3d --- /dev/null +++ b/src/Factory.ts @@ -0,0 +1,13 @@ +import SuperClient from './SuperClient.js'; + +export default abstract class Factory { + protected readonly client: SuperClient; + + constructor(client: SuperClient) { + this.client = client; + } + + protected getClient(): SuperClient { + return this.client; + } +} diff --git a/src/InterChat.ts b/src/InterChat.ts new file mode 100644 index 00000000..dd049801 --- /dev/null +++ b/src/InterChat.ts @@ -0,0 +1,53 @@ +import db from './utils/Db.js'; +import SuperClient from './SuperClient.js'; +import CommandHandler from './structures/CommandHandler.js'; +import { NetworkMessage } from './structures/NetworkManager.js'; + +class InterChat extends SuperClient { + public constructor() { + super(); + + this.on('ready', () => { + // initialize the client + this.init(); + + // load commands + CommandHandler.loadCommandFiles(); + + this.logger.info( + `Logged in as ${this.user?.tag}! Cached ${this.guilds.cache.size} guilds on Cluster ${this.cluster?.id}.`, + ); + }); + + // delete guild from database + this.on('guildDelete', async (guild) => { + this.logger.info(`Left ${guild.name} (${guild.id})`); + await db.connectedList.deleteMany({ where: { serverId: guild.id } }); + }); + + // handle slash/ctx commands + this.on('interactionCreate', (interaction) => this.getCommandManager().handleInteraction(interaction)); + + // handle network reactions + this.on('messageReactionAdd', (reaction, user) => this.getReactionUpdater().listen(reaction, user)); + + // handle network messages + this.on('messageCreate', async (message) => { + if (message.author.bot || message.system || message.webhookId) return; + + const isNetworkMessage = await db.connectedList.findFirst({ + where: { channelId: message.channel.id, connected: true }, + include: { hub: true }, + }); + + if (!isNetworkMessage) return; + + this.getNetworkManager().handleNetworkMessage(message as NetworkMessage, isNetworkMessage); + }); + + } +} + +const client = new InterChat(); + +client.login(process.env.TOKEN); diff --git a/src/Scripts/badge/add.ts b/src/Scripts/badge/add.ts deleted file mode 100644 index efb60141..00000000 --- a/src/Scripts/badge/add.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ChatInputCommandInteraction, User } from 'discord.js'; -import { getDb } from '../../Utils/utils'; - -export default { - async execute( - interaction: ChatInputCommandInteraction, - user: User, - badge: string, - ) { - const db = getDb(); - const userInCollection = await db.userBadges.findFirst({ where: { userId: user.id } }); - - if (userInCollection) { - const userBadges = userInCollection.badges; - - if (userBadges.includes(badge)) { - await interaction.reply('User already has the badge.'); - return; - } - else { - await db.userBadges.update({ where: { userId: user.id }, data: { badges: [...userBadges, badge] } }); - await interaction.reply(`Badge \`${badge}\` added to ${user.username}.`); - return; - } - } - else { - await db.userBadges.create({ data: { userId: user.id, badges: [badge] } }); - await interaction.reply(`Badge \`${badge}\` added to ${user.username}.`); - return; - } - }, -}; diff --git a/src/Scripts/badge/list.ts b/src/Scripts/badge/list.ts deleted file mode 100644 index 31a41e9a..00000000 --- a/src/Scripts/badge/list.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ChatInputCommandInteraction, User } from 'discord.js'; -import { getDb } from '../../Utils/utils'; - -export default { - async execute( - interaction: ChatInputCommandInteraction, - user: User, - ) { - const db = getDb(); - const userInCollection = await db.userBadges.findFirst({ where: { userId: user.id } }); - if (!userInCollection) { - await interaction.reply(`User ${user.username} doesn't have any badges!`); - } - else { - const badges = userInCollection.badges; - if (badges.length === 0) { - await interaction.reply(`User ${user.username} doesn't have any badges!`); - } - else { - const badgeList = badges.map((badge: string) => `\`${badge}\``); - await interaction.reply(`User ${user.username} has the badges ${badgeList.join(', ')}.`); - } - } - }, -}; diff --git a/src/Scripts/badge/remove.ts b/src/Scripts/badge/remove.ts deleted file mode 100644 index 24bf078f..00000000 --- a/src/Scripts/badge/remove.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ChatInputCommandInteraction, User } from 'discord.js'; -import { getDb } from '../../Utils/utils'; - -export default { - async execute( - interaction: ChatInputCommandInteraction, - user: User, - badge: string, - ) { - const db = getDb(); - const userInCollection = await db.userBadges.findFirst({ where: { userId: user.id } }); - - if (userInCollection) { - const userBadges = userInCollection.badges; - - if (userBadges.includes(badge)) { - userBadges.splice(userBadges.indexOf(badge), 1); - await db.userBadges.update({ where: { userId: user.id }, data: { badges: userBadges } }); - await interaction.reply(`Removed badge \`${badge}\` from user ${user.username}.`); - } - else { - await interaction.reply(`User ${user.username} does not have the badge ${badge}.`); - } - } - else { - await interaction.reply(`User ${user.username} does not have the badge ${badge}.`); - } - }, -}; diff --git a/src/Scripts/blacklist/list.ts b/src/Scripts/blacklist/list.ts deleted file mode 100644 index c2857cfb..00000000 --- a/src/Scripts/blacklist/list.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { hubs } from '@prisma/client'; -import { stripIndents } from 'common-tags'; -import { APIEmbedField, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { paginate } from '../../Utils/paginator'; -import { constants, getDb } from '../../Utils/utils'; - -module.exports = { - async execute(interaction: ChatInputCommandInteraction, hub: hubs) { - const serverOpt = interaction.options.getString('type'); - - const embeds: EmbedBuilder[] = []; - let fields: APIEmbedField[] = []; - - const LIMIT = 5; - let counter = 0; - - // loop through all data - // after counter hits limit (5) assign fields to an embed and push to to embeds array - // reset counter & clear fields array - // repeat until you reach the end - - if (serverOpt == 'server') { - const result = await getDb().blacklistedServers.findMany({ where: { hubs: { some: { hubId: hub.id } } } }); - - result.forEach((data, index) => { - const hubData = data.hubs.find(({ hubId }) => hubId === hub.id); - fields.push({ - name: data.serverName, - value: stripIndents` - **ServerId:** ${data.serverId} - **Reason:** ${hubData?.reason} - **Expires:** ${!hubData?.expires ? 'Never.' : ``} - `, - }); - - counter++; - if (counter >= LIMIT || index === result.length - 1) { - embeds.push(new EmbedBuilder() - .setFields(fields) - .setColor('#0099ff') - .setAuthor({ - name: 'Blacklisted Servers:', - iconURL: interaction.client.user?.avatarURL()?.toString(), - })); - - counter = 0; - fields = []; - } - }); - } - else if (serverOpt == 'user') { - const result = await getDb().blacklistedUsers.findMany({ where: { hubs: { some: { hubId: hub.id } } } }); - - result.forEach((data, index) => { - const hubData = data.hubs.find(({ hubId }) => hubId === hub.id); - - fields.push({ - name: data.username, - value: stripIndents` - **UserID:** ${data.userId} - **Reason:** ${hubData?.reason} - **Expires:** ${!hubData?.expires ? 'Never.' : ``} - `, - }); - - counter++; - if (counter >= LIMIT || index === result.length - 1) { - embeds.push(new EmbedBuilder() - .setFields(fields) - .setColor(constants.colors.interchatBlue) - .setAuthor({ - name: 'Blacklisted Users:', - iconURL: interaction.client.user?.avatarURL()?.toString(), - })); - - counter = 0; - fields = []; - } - }); - } - - paginate(interaction, embeds); - }, -}; diff --git a/src/Scripts/blacklist/server.ts b/src/Scripts/blacklist/server.ts deleted file mode 100644 index f9137df6..00000000 --- a/src/Scripts/blacklist/server.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import { addServerBlacklist, fetchServerBlacklist, notifyBlacklist, removeBlacklist, scheduleUnblacklist } from '../../Utils/blacklist'; -import { hubs } from '@prisma/client'; -import { captureException } from '@sentry/node'; -import parse from 'parse-duration'; -import logger from '../../Utils/logger'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction, hub: hubs) { - // defer the reply as it may take a while to fetch and stuff - await interaction.deferReply(); - - const db = getDb(); - const subCommandGroup = interaction.options.getSubcommandGroup(); - const serverOpt = interaction.options.getString('server', true); - - if (subCommandGroup == 'add') { - const reason = interaction.options.getString('reason', true); - const duration = parse(`${interaction.options.getString('duration')}`); - const expires = duration ? new Date(Date.now() + duration) : undefined; - - const serverInBlacklist = await fetchServerBlacklist(hub.id, serverOpt); - if (serverInBlacklist) return await interaction.followUp('The server is already blacklisted.'); - - const server = await interaction.client.guilds.fetch(serverOpt).catch(() => null); - if (!server) return interaction.followUp('You have inputted an invalid server ID.'); - - try { - await addServerBlacklist(server.id, interaction.user, hub.id, reason, expires); - } - catch (err) { - logger.error(err); - captureException(err); - interaction.followUp(`Failed to blacklist **${server.name}**. Enquire with the bot developer for more information.`); - return; - } - - if (expires && interaction.guildId) scheduleUnblacklist('server', interaction.client, interaction.guildId, hub.id, expires); - - const successEmbed = new EmbedBuilder() - .setDescription(`${emojis.normal.tick} **${server.name}** has been successfully blacklisted!`) - .setColor('Green') - .addFields( - { - name: 'Reason', - value: reason ? reason : 'No reason provided.', - inline: true, - }, - { - name: 'Expires', - value: expires ? `` : 'Never.', - inline: true, - }, - ); - - await interaction.followUp({ embeds: [successEmbed] }); - - const connected = await db.connectedList.findFirst({ where: { serverId: serverOpt, hubId: hub.id } }); - if (connected) { - // notify the server that they have been blacklisted - const channel = await interaction.client.channels.fetch(connected.channelId).catch(() => null); - if (channel?.isTextBased()) notifyBlacklist(channel, hub.id, expires, reason).catch(() => null); - - // delete the connected channel from db so they can't reconnect - await db.connectedList.delete({ where: { channelId: connected.channelId } }); - } - } - - else if (subCommandGroup == 'remove') { - const blacklistedServer = await db.blacklistedServers.findFirst({ where: { serverId: serverOpt } }); - if (!blacklistedServer) return await interaction.followUp({ content: 'The server is not blacklisted.', ephemeral: true }); - - await removeBlacklist('server', hub.id, blacklistedServer.serverId); - - // Using name from DB since the bot can't access server through API. - interaction.followUp(`The server **${blacklistedServer.serverName}** has been removed from the blacklist.`); - } - }, -}; diff --git a/src/Scripts/blacklist/user.ts b/src/Scripts/blacklist/user.ts deleted file mode 100644 index 9416ecaa..00000000 --- a/src/Scripts/blacklist/user.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { hubs } from '@prisma/client'; -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { modActions } from '../networkLogs/modActions'; -import { addUserBlacklist, fetchUserBlacklist, notifyBlacklist, removeBlacklist, scheduleUnblacklist } from '../../Utils/blacklist'; -import emojis from '../../Utils/JSON/emoji.json'; -import parse from 'parse-duration'; - -export default { - async execute(interaction: ChatInputCommandInteraction, hub: hubs) { - await interaction.deferReply(); - - const subcommandGroup = interaction.options.getSubcommandGroup(); - const userId = interaction.options.getString('user', true); - const reason = interaction.options.getString('reason'); - const duration = parse(`${interaction.options.getString('duration')}`); - - const expires = duration ? new Date(Date.now() + duration) : undefined; - - if (subcommandGroup == 'add') { - // get ID if user inputted a @ mention - const userOpt = userId.replaceAll(/<@|!|>/g, ''); - // find user through username if they are cached or fetch them using ID - const user = interaction.client.users.cache.find((u) => u.username === userOpt) ?? - await interaction.client.users.fetch(userOpt).catch(() => null); - - if (!user) return interaction.followUp('Could not find user. Use an ID instead.'); - if (user.id === interaction.user.id) return interaction.followUp('You cannot blacklist yourself.'); - if (user.id === interaction.client.user?.id) return interaction.followUp('You cannot blacklist the bot wtf.'); - - const userInBlacklist = await fetchUserBlacklist(hub.id, userOpt); - if (userInBlacklist) { - interaction.followUp(`**${user.username}** is already blacklisted.`); - return; - } - - await addUserBlacklist(hub.id, interaction.user, user, String(reason), expires); - if (expires) scheduleUnblacklist('user', interaction.client, user.id, hub.id, expires); - notifyBlacklist(user, hub.id, expires, String(reason)); - - const successEmbed = new EmbedBuilder() - .setDescription(`${emojis.normal.tick} **${user.username}** has been successfully blacklisted!`) - .setColor('Green') - .addFields( - { - name: 'Reason', - value: reason ? reason : 'No reason provided.', - inline: true, - }, - { - name: 'Expires', - value: expires ? `` : 'Never.', - inline: true, - }, - ); - - await interaction.followUp({ embeds: [successEmbed] }); - } - - - else if (subcommandGroup == 'remove') { - const blacklistedUser = await fetchUserBlacklist(hub.id, userId); - const user = await interaction.client.users.fetch(userId).catch(() => null); - - if (!blacklistedUser) return interaction.followUp('The inputted user is not blacklisted.'); - - await removeBlacklist('user', hub.id, blacklistedUser.userId); - await interaction.followUp(`**${user?.username || blacklistedUser?.username}** has been removed from the blacklist.`); - - if (user) { - modActions(interaction.user, { - user, - action: 'unblacklistUser', - blacklistedFor: blacklistedUser.hubs.find(({ hubId }) => hubId === hub.id)?.reason, - hubId: hub.id, - }); - } - } - }, -}; diff --git a/src/Scripts/find/server.ts b/src/Scripts/find/server.ts deleted file mode 100644 index 12e366c9..00000000 --- a/src/Scripts/find/server.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { EmbedBuilder, ChatInputCommandInteraction, Guild, GuildMember } from 'discord.js'; -import { stripIndents } from 'common-tags'; -import { constants, getDb, toTitleCase } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction, serverId: string, hidden: boolean) { - await interaction.deferReply({ ephemeral: hidden }); - - const server = await interaction.client.guilds.fetch(serverId).catch(() => null); - if (!server) return interaction.editReply('Unknown Server.'); - - const owner = await server?.fetchOwner(); - - await interaction.editReply({ - content: server?.id, embeds: [await embedGen(server, owner)], - }); - - }, -}; - -async function embedGen(guild: Guild, GuildOwner: GuildMember | undefined) { - const { blacklistedServers, connectedList } = getDb(); - - const guildInDb = await connectedList.findMany({ where: { serverId: guild.id }, include: { hub: true } }); - const guildBlacklisted = await blacklistedServers.count({ where: { serverId: guild.id } }); - const guildBoostLevel = guild.premiumTier === 0 - ? 'None' : guild.premiumTier === 1 - ? 'Level 1' - : guild.premiumTier === 2 ? 'Level 2' - : guild.premiumTier === 3 ? 'Level 3' - : 'Unknown'; - - const { no } = emojis.normal; - - return new EmbedBuilder() - .setAuthor({ name: `${guild.name}`, iconURL: guild.iconURL() || undefined }) - .setDescription(guild.description || 'No Description') - .setColor(constants.colors.invisible) - .setThumbnail(guild.iconURL() || null) - .setImage(guild.bannerURL({ size: 1024 }) || null) - .addFields([ - { - name: 'Server Info', - value: stripIndents` - > **Server ID:** ${guild.id} - > **Owner:** @${GuildOwner?.user.username} (${GuildOwner?.id}) - > **Created:** - > **Language:** ${guild.preferredLocale} - > **Boost Level:** ${guildBoostLevel} - > **Member Count:** ${guild.memberCount} - `, - }, - - { - name: 'Server Features:', - value: guild.features.map(feat => '> ' + toTitleCase(feat.replaceAll('_', ' ')) + '\n') - .join('') || `> ${no} No Features Enabled`, - }, - - { - name: 'Network Info', - value: stripIndents` - > **Joined Hubs(${guildInDb.length}):** ${ guildInDb.map(({ hub }) => hub?.name).join(', ')} - > **Blacklisted from:** ${guildBlacklisted} hubs - > **Channel(s):** ${guildInDb?.map(({ channelId }) => `<#${channelId}> (${channelId}))`)}`, - }, - ]); -} - diff --git a/src/Scripts/find/user.ts b/src/Scripts/find/user.ts deleted file mode 100644 index f9a23ff4..00000000 --- a/src/Scripts/find/user.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { EmbedBuilder, ChatInputCommandInteraction, User } from 'discord.js'; -import { stripIndents } from 'common-tags'; -import { constants, getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - - -const embedGen = async (user: User) => { - const userInBlacklist = await getDb().blacklistedUsers?.findFirst({ where: { userId: user.id } }); - - const owns = user.client.guilds.cache - .filter((guild) => guild.ownerId == user.id) - .map((guild) => guild.name); - - const { icons } = emojis; - - return new EmbedBuilder() - .setAuthor({ name: user.username, iconURL: user.avatarURL()?.toString() }) - .setColor(constants.colors.invisible) - .setImage(user.bannerURL({ size: 1024 }) || null) - .setThumbnail(user.avatarURL()) - .addFields([ - { - name: 'User', - value: stripIndents` - > ${icons.mention} **Username:** ${user.username} - > ${icons.id} **ID:** ${user.id} - > ${icons.members} **Created:** - > ${icons.bot} **Bot:** ${user.bot}`, - }, - { - name: 'Network', - value: stripIndents` - > ${icons.owner} **Owns:** ${owns.length > 0 ? owns.join(', ') : 'None'} - > ${icons.delete} **Blacklisted:** ${userInBlacklist ? 'Yes' : 'No'}`, - }, - ]); -}; - - -export default { - async execute(interaction: ChatInputCommandInteraction, userId: string, hidden: boolean) { - const user = await interaction.client.users.fetch(userId).catch(() => null); - if (!user) return interaction.reply({ content: 'Unknown user. Try using user\'s ID instead if you used username.', ephemeral: true }); - - await interaction.reply({ - content: user.id, - embeds: [await embedGen(user)], - ephemeral: hidden, - }); - - }, -}; - diff --git a/src/Scripts/hub/browse.ts b/src/Scripts/hub/browse.ts deleted file mode 100644 index fca99e89..00000000 --- a/src/Scripts/hub/browse.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, ChatInputCommandInteraction, EmbedBuilder, GuildTextBasedChannel, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; -import { calculateAverageRating, createHubListingsEmbed, getDb } from '../../Utils/utils'; -import { paginate } from '../../Utils/paginator'; -import { hubs } from '@prisma/client'; -import { captureException } from '@sentry/node'; -import logger from '../../Utils/logger'; -import emojis from '../../Utils/JSON/emoji.json'; -import onboarding from '../network/onboarding'; -import { createConnection } from '../../Utils/network'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const sortBy = interaction.options.getString('sort') as - | 'connections' - | 'active' - | 'popular' - | 'recent' - | undefined; - const hubName = interaction.options.getString('search') || undefined; - - const db = getDb(); - let sortedHubs: hubs[] = []; - - switch (sortBy) { - case 'popular': - sortedHubs = ( - await db.hubs.findMany({ - where: { name: hubName, private: false }, - include: { connections: true }, - }) - ).sort((a, b) => { - const aAverage = calculateAverageRating(a.rating.map((rating) => rating.rating)); - const bAverage = calculateAverageRating(b.rating.map((rating) => rating.rating)); - return bAverage - aAverage; - }); - break; - case 'recent': - sortedHubs = await db.hubs.findMany({ - where: { name: hubName, private: false }, - orderBy: { createdAt: 'desc' }, - }); - break; - case 'connections': - sortedHubs = await db.hubs.findMany({ - where: { name: hubName, private: false }, - orderBy: { connections: { _count: 'desc' } }, - }); - break; - - case 'active': - default: - sortedHubs = await db.hubs.findMany({ - where: { name: hubName, private: false }, - orderBy: { messages: { _count: 'desc' } }, - }); - break; - } - - const hubList = await Promise.all( - sortedHubs?.map(async (hub) => { - const connections = await db.connectedList - .count({ where: { hubId: hub.id, connected: true } }) - .catch(() => 0); - - return createHubListingsEmbed(hub, { connections }); - }), - ); - - if (!hubList || hubList.length === 0) { - interaction.reply({ - content: 'There are no hubs listed here at the moment. Please try again later!', - ephemeral: true, - }); - return; - } - - const paginateBtns = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`rate-${sortedHubs[0].id}`) - .setLabel('Rate') - .setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId(`join-${sortedHubs[0].id}`) - .setLabel('Join') - .setStyle(ButtonStyle.Success), - ); - - paginate(interaction, hubList, { - extraComponent: { - actionRow: [paginateBtns], - updateComponents(pageNumber) { - paginateBtns.components[0].setCustomId(`rate-${sortedHubs[pageNumber].id}`); - paginateBtns.components[1].setCustomId(`join-${sortedHubs[pageNumber].id}`); - return paginateBtns; - }, - async execute(i: ButtonInteraction) { - if (i.customId.startsWith('rate-')) { - const ratingModal = new ModalBuilder() - .setCustomId(i.id) - .setTitle('Rate Hub') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('rating') - .setLabel('Rating') - .setPlaceholder('Rate the hub from 1-5') - .setMaxLength(1) - .setValue('5') - .setStyle(TextInputStyle.Short) - .setRequired(true), - ), - ); - await i.showModal(ratingModal); - i.awaitModalSubmit({ time: 30_000 }) - .then(async (m) => { - const rating = parseInt(m.fields.getTextInputValue('rating')); - if (isNaN(rating) || rating < 1 || rating > 5) { - return m.reply({ - content: 'Invalid rating. You must enter a number between 1 and 5.', - ephemeral: true, - }); - } - const hubId = i.customId.replace('rate-', ''); - const hubDetails = await db.hubs.findFirst({ where: { id: hubId } }); - - if (!hubDetails) { - m.reply({ - content: 'Hub not found.', - ephemeral: true, - }); - return; - } - - const userAlreadyRated = hubDetails.rating.find((r) => r.userId === i.user.id); - - await db.hubs.update({ - where: { id: hubId }, - data: { - rating: !userAlreadyRated - ? { push: { userId: i.user.id, rating } } - : { updateMany: { where: { userId: i.user.id }, data: { rating } } }, - }, - }); - - await m.reply({ - content: 'Rating submitted. Thank you!', - ephemeral: true, - }); - }) - .catch((e) => { - if (!e.message.includes('ending with reason: time')) { - logger.error(e); - captureException(e, { - user: { username: i.user.username, id: i.user.id }, - extra: { context: 'Rating modal' }, - }); - } - }); - } - else if (i.customId.startsWith('join-')) { - const hubDetails = await db.hubs.findFirst({ - where: { id: i.customId.replace('join-', '') }, - include: { connections: true }, - }); - - if (!hubDetails) { - i.reply({ - content: 'Hub not found.', - ephemeral: true, - }); - return; - } - - const alreadyJoined = hubDetails.connections.find((c) => c.serverId === i.guildId); - if (alreadyJoined) { - i.reply({ - content: `You have already joined **${hubDetails.name}** from <#${alreadyJoined.channelId}>!`, - ephemeral: true, - }); - return; - } - - let channel = i.channel; - - const channelSelect = new ActionRowBuilder().addComponents( - new ChannelSelectMenuBuilder() - .setCustomId('channel_select') - .setPlaceholder('Select a different channel.') - .setChannelTypes([ - ChannelType.PublicThread, - ChannelType.PrivateThread, - ChannelType.GuildText, - ]), - ); - - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm') - .setStyle(ButtonStyle.Success), - new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Danger), - ); - - // use current channel embed - const embed = new EmbedBuilder() - .setDescription(` - Are you sure you wish to join **${hubDetails.name}** from ${interaction.channel}? - - **Note:** You can always change this later using \`/network manage\`. - `, - ) - .setColor('Aqua') - .setFooter({ text: 'Use a different channel? Use the dropdown below.' }); - - const reply = await i.reply({ - embeds: [embed], - components: [channelSelect, buttons], - fetchReply: true, - ephemeral: true, - }); - - const response = await reply - .awaitMessageComponent({ - time: 60_000 * 2, - filter: (e) => e.user.id === i.user.id, - }) - .catch(() => null); - - if (!response?.inCachedGuild() || response.customId === 'cancel') { - i.deleteReply().catch(() => null); - return; - } - - if (response.isChannelSelectMenu()) { - channel = response.guild.channels.cache.get(response.values[0]) as GuildTextBasedChannel; - } - - - if (channel?.type !== ChannelType.GuildText && !channel?.isThread()) { - await response.update(`${emojis.normal.no} Only text and thread channels are supported!`); - return; - } - - if (!interaction.guild?.members.me?.permissionsIn(channel).has(['ManageWebhooks'])) { - await response.update(`${emojis.normal.no} I need to have the \`Manage Webhooks\` permission in ${channel} to connect it to a hub!`); - return; - } - - if (!response.member.permissionsIn(channel).has('ManageChannels')) { - await response.update(`${emojis.normal.no} You need to have the \`Manage Channels\` permission in ${channel} to connect it to a hub!`); - return; - } - - if ( - (response.customId === 'confirm' || response.customId === 'channel_select') - ) { - const channelConnected = await db.connectedList.findFirst({ - where: { channelId: channel.id }, - }); - - if (channelConnected) { - response.update({ - content: 'This channel is already connected to another hub!', - embeds: [], - components: [], - }); - return; - } - - // Show new users rules & info about network - const onboardingStatus = await onboarding.execute(response, hubDetails.name, channel.id, true); - // if user cancelled onboarding or didn't click any buttons, stop here - if (!onboardingStatus) return interaction.deleteReply().catch(() => null); - - createConnection(response.guild, hubDetails, channel).then((success) => { - if (success) { - response.editReply({ - content: `Successfully joined hub ${hubDetails.name} from ${channel}! Use \`/network manage\` to manage your connection. And \`/hub leave\` to leave the hub.`, - embeds: [], - components: [], - }); - return; - } - response.message.delete().catch(() => null); - }); - } - } - }, - }, - }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/connections.ts b/src/Scripts/hub/connections.ts deleted file mode 100644 index bc90af83..00000000 --- a/src/Scripts/hub/connections.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { getDb, getGuildName } from '../../Utils/utils'; -import { paginate } from '../../Utils/paginator'; -import { stripIndent } from 'common-tags'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - await interaction.deferReply(); - - const db = getDb(); - const hub = interaction.options.getString('hub', true); - const allNetworks = await db.connectedList.findMany({ - where: { - hub: { - name: hub, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - }, - orderBy: { date: 'asc' }, - }); - - if (allNetworks.length === 0) return interaction.editReply(`No connected servers yet ${emojis.normal.bruhcat}`); - - const embeds: EmbedBuilder[] = []; - let itemsPerPage = 5; - - - for (let index = 0; index < allNetworks.length; index += 5) { - const current = allNetworks?.slice(index, itemsPerPage); - - let j = index; - let l = index; - itemsPerPage += 5; - - const fields = current.map(connection => { - const serverName = getGuildName(interaction.client, connection.serverId); - const channelName = interaction.client.channels.cache.get(connection.channelId); - const setup = allNetworks.find((settings) => settings.channelId === connection.channelId); - let value = stripIndent` - ServerID: ${connection.serverId} - Channel: ${channelName} \`(${connection.channelId}\`) - `; - if (setup) { - value += '\n' + stripIndent` - Joined At: - Invite: ${setup.invite ? `https://discord.gg/${setup.invite}` : 'Not Set.'}`; - } - - return { name: `${++j}. ${serverName}`, value }; - }); - - embeds.push( - new EmbedBuilder() - .setDescription(`Current connected servers: ${++l}-${j} / **${allNetworks.length}**`) - .setColor(0x2F3136) - .setFields(fields), - ); - } - - return paginate(interaction, embeds); - }, -}; diff --git a/src/Scripts/hub/create.ts b/src/Scripts/hub/create.ts deleted file mode 100644 index 6412111d..00000000 --- a/src/Scripts/hub/create.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { ChatInputCommandInteraction, ModalBuilder, TextInputBuilder, EmbedBuilder, ActionRowBuilder, TextInputStyle, Collection } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import { HubSettingsBits } from '../../Utils/hubSettingsBitfield'; -import { stripIndents } from 'common-tags'; - -const cooldowns = new Collection(); - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const commandInCooldown = cooldowns.get(interaction.user.id); - if (commandInCooldown && commandInCooldown > Date.now()) { - return await interaction.reply({ - content: `You may create another hub .`, - ephemeral: true, - }); - } - if (!interaction.inCachedGuild()) return; - - const hubName = interaction.options.getString('name', true); - const iconUrl = interaction.options.getString('icon'); - const bannerUrl = interaction.options.getString('banner'); - - const imgurRegex = /\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g; - - const imgurIcons = iconUrl?.match(imgurRegex); - const imgurBanners = bannerUrl?.match(imgurRegex); - - if (imgurIcons === null || imgurBanners === null) { - return await interaction.reply({ - content: 'Please provide a valid Imgur link for the icon and banner. It should start with `https://i.imgur.com/` and end with an image extension.', - ephemeral: true, - }); - } - - // if hubName contains "discord", "clyde" "```" then return - if (hubName.match(/discord|clyde|```/gi)) { - return await interaction.reply({ - content: 'Hub name can not contain `discord`, `clyde` or \\`\\`\\` . Please choose another name.', - ephemeral: true, - }); - } - - const db = getDb(); - const hubs = await db.hubs.findMany({ where: { OR: [{ ownerId: interaction.user.id }, { name: hubName }] } }); - - if (hubs.find(hub => hub.name === hubName)) { - return await interaction.reply({ - content: `Sorry, name **${hubName}** is unavailable! Please choose another name.`, - ephemeral: true, - }); - } - - else if (hubs.reduce((acc, hub) => hub.ownerId === interaction.user.id ? acc + 1 : acc, 0) >= 3) { - return await interaction.reply({ - content: 'You may only create a maximum of **3** hubs at the moment. Please delete one of your existing hubs before creating a new one.', - ephemeral: true, - }); - } - - const modal = new ModalBuilder() - .setTitle('Create a hub') - .setCustomId(interaction.id) - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setLabel('What is the hub about?') - .setPlaceholder('A detailed description about your hub.') - .setMaxLength(1024) - .setStyle(TextInputStyle.Paragraph) - .setCustomId('description'), - ), - // new ActionRowBuilder().addComponents( - // new TextInputBuilder() - // .setLabel('Language') - // .setPlaceholder('Pick the language of the hub.') - // .setStyle(TextInputStyle.Short) - // .setCustomId('language'), - // ), - ); - - await interaction.showModal(modal); - - interaction.awaitModalSubmit({ time: 60 * 5000 }) - .then(async submitIntr => { - await submitIntr.deferReply({ ephemeral: true }); - - const description = submitIntr.fields.getTextInputValue('description'); - - await db.hubs.create({ - data: { - name: hubName, - description, - private: true, - ownerId: submitIntr.user.id, - iconUrl: imgurIcons?.at(0) ?? interaction.client.user.displayAvatarURL(), - bannerUrl: imgurBanners?.[0], - settings: HubSettingsBits.SpamFilter | HubSettingsBits.Reactions, - }, - }); - - // FIXME this is a temp cooldown until we have a global cooldown system for commands & subcommands - cooldowns.set(interaction.user.id, Date.now() + 60 * 60 * 1000); - const successEmbed = new EmbedBuilder() - .setColor('Green') - .setDescription(stripIndents` - ### Hub Created! - - Congratulations! Your private hub, **${hubName}**, has been successfully created. - To join, create an invite using \`/hub invite create\` and share the generated code. Then join using \`/hub join\`. - - - **Generate invite:** \`/hub invite create\` - - **Go public:** \`/hub manage\` - - **Join hub:** \`/hub join\` - - **Edit hub:** \`/hub manage\` - - **Add moderators:** \`/hub moderator add\` - - __Learn more about hubs in our [guide](https://discord-interchat.github.io/docs).__ - `) - - .setFooter({ text: 'Join the support server for help!' }) - .setTimestamp(); - - await submitIntr.editReply({ embeds: [successEmbed] }); - }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/delete.ts b/src/Scripts/hub/delete.ts deleted file mode 100644 index 300fb413..00000000 --- a/src/Scripts/hub/delete.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { captureException } from '@sentry/node'; -import { ChatInputCommandInteraction, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js'; -import { deleteHubs, getDb } from '../../Utils/utils'; -import logger from '../../Utils/logger'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction, hubName: string) { - const db = getDb(); - const hubInDb = await db.hubs.findFirst({ where: { name: hubName } }); - - if (interaction.user.id !== hubInDb?.ownerId) { - return await interaction.reply({ - content: 'Only the hub owner can delete this hub.', - ephemeral: true, - }); - } - - const confirmEmbed = new EmbedBuilder() - .setTitle('Are you sure?') - .setDescription('Are you sure you want to delete this hub? This is a destructive action that will **delete all connections** along with the hub.') - .setColor('Red'); - const confirmButtons = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setLabel('Confirm') - .setCustomId('confirm') - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel('Cancel') - .setCustomId('cancel') - .setStyle(ButtonStyle.Secondary), - ); - - const msg = await interaction.reply({ - embeds: [confirmEmbed], - components: [confirmButtons], - }); - - const clicked = await msg.awaitMessageComponent({ - filter: b => b.user.id === interaction.user.id, - time: 30_000, - componentType: ComponentType.Button, - }).catch(() => null); - - if (!clicked || clicked.customId === 'cancel') { - await msg.delete().catch(() => null); - return; - } - - await clicked.update(`${emojis.normal.loading} Deleting connections, invites, messages and the hub. Please wait...`); - - try { - await deleteHubs([hubInDb?.id]); - } - catch (e) { - logger.error(e); - captureException(e, { user: { id: interaction.user.id, username: interaction.user.username } }); - await clicked.editReply('Something went wrong while trying to delete the hub. The developers have been notified.'); - return; - } - - await clicked.editReply({ - content:`${emojis.normal.tick} The hub has been successfully deleted.`, - embeds: [], - components: [], - }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/invite.ts b/src/Scripts/hub/invite.ts deleted file mode 100644 index f4696910..00000000 --- a/src/Scripts/hub/invite.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { captureException } from '@sentry/node'; -import { logger } from '@sentry/utils'; -import { stripIndents } from 'common-tags'; -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const db = getDb(); - const subcommand = interaction.options.getSubcommand(); - - switch (subcommand) { - case 'create': { - const expires = new Date(); - const hubName = interaction.options.getString('hub', true); - const hours = interaction.options.getNumber('expiry'); - hours - ? expires.setHours(expires.getHours() + hours) - : expires.setHours(expires.getHours() + 24); - - const hubInDb = await db.hubs.findFirst({ - where: { - name: hubName, - private: true, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, - ], - }, - }); - - if (!hubInDb) { - await interaction.reply({ - content: `${emojis.normal.no} Invalid Hub Provided. Make sure provided hub is one that you have permissions in the hub and that it is private.`, - ephemeral: true, - }); - return; - } - const createdInvite = await db.hubInvites.create({ - data: { - expires, - hub: { connect: { name: hubName } }, - }, - }); - - const embed = new EmbedBuilder() - .setTitle('Invite Created') - .setDescription(stripIndents` - Give this code to someone who wishes to join the hub. This invite has unlimited uses. - - Join using: \`/hub join invite:${createdInvite.code}\` - - **Code:** \`${createdInvite.code}\` - **Expiry ** - `) - .setColor('Green') - .setTimestamp(); - - await interaction.reply({ - embeds: [embed], - ephemeral: true, - }); - break; - } - - case 'revoke': { - const code = interaction.options.getString('code', true); - const inviteInDb = await db.hubInvites.findFirst({ - where: { - code, - hub: { - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, - ], - }, - }, - }); - - - if (!inviteInDb) { - await interaction.reply({ - content: `${emojis.normal.no} Invalid Invite Code.`, - ephemeral: true, - }); - return; - } - - try { - await db.hubInvites.delete({ where: { code } }); - await interaction.reply({ - content: `Successfully revoked invite \`${code}\`!`, - ephemeral: true, - }); - } - catch (e) { - logger.error(e); - captureException(e); - await interaction.reply({ - content: 'An error occoured while trying to revoke invite! The developers have been notified.', - ephemeral: true, - }).catch(() => null); - return; - } - break; - } - - case 'list': { - const hubName = interaction.options.getString('hub', true); - const hubInDb = await db.hubs.findFirst({ - where: { - name: hubName, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, - ], - }, - }); - - if (!hubInDb?.private) { - await interaction.reply({ - content: 'You can only view invite codes for private hubs.', - ephemeral: true, - }); - return; - } - - const invitesInDb = await db.hubInvites.findMany({ where: { hubId: hubInDb.id } }); - if (invitesInDb.length === 0) { - await interaction.reply({ - content: `${emojis.normal.yes} There are no invites to this hub yet.`, - ephemeral: true, - }); - return; - } - - const inviteArr = invitesInDb.map((inv, index) => - `${index + 1}. \`${inv.code}\` - `, - ); - - const inviteEmbed = new EmbedBuilder() - .setTitle('Invite Codes') - .setDescription(inviteArr.join('\n')) - .setColor('Yellow') - .setTimestamp(); - - await interaction.reply({ - embeds: [inviteEmbed], - ephemeral: true, - }); - break; - } - - default: - break; - } - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/join.ts b/src/Scripts/hub/join.ts deleted file mode 100644 index 369bec70..00000000 --- a/src/Scripts/hub/join.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { ChatInputCommandInteraction, ChannelType } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import displaySettings from '../network/displaySettings'; -import emojis from '../../Utils/JSON/emoji.json'; -import onboarding from '../network/onboarding'; -import { fetchServerBlacklist, fetchUserBlacklist } from '../../Utils/blacklist'; -import { createConnection } from '../../Utils/network'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - if (!interaction.inCachedGuild()) return; - - const db = getDb(); - const name = interaction.options.getString('name') || undefined; - const invite = interaction.options.getString('invite') || undefined; - const channel = interaction.options.getChannel('channel', true, [ChannelType.GuildText, ChannelType.PublicThread, ChannelType.PrivateThread]); - let hubExists; - - if (!interaction.member.permissionsIn(channel).has(['ManageChannels'])) { - return await interaction.reply({ - content: `${emojis.normal.no} You need to have the \`Manage Channels\` permission in ${channel} to connect it to a hub!`, - ephemeral: true, - }); - } - - if (!invite && !name) { - return await interaction.reply({ - content: `${emojis.normal.no} You need to provide either a hub name or invite!`, - ephemeral: true, - }); - } - const channelConnected = await db.connectedList.findFirst({ where: { channelId: channel.id } }); - if (channelConnected) { - return await interaction.reply({ - content: `${channel} is already part of a hub! Please leave the hub or choose a different channel.`, - ephemeral: true, - }); - } - - if (invite) { - const inviteExists = await db.hubInvites.findFirst({ - where: { code: invite }, - include: { hub: { include: { connections: true } } }, - }); - - if (!inviteExists) { - return await interaction.reply({ - content: `${emojis.normal.no} Invalid invite code. Please recheck if that code is correct.`, - ephemeral: true, - }); - } - const guildInHub = inviteExists.hub.connections.find((c) => c.serverId === channel.guildId); - if (guildInHub) { - return await interaction.reply({ - content: `This server has already joined hub **${inviteExists.hub.name}** from <#${guildInHub.channelId}>! Please leave the hub from that channel first, or change the channel using \`/network manage\`.!`, - ephemeral: true, - }); - } - - hubExists = inviteExists?.hub; - } - - else if (name) { - hubExists = await db.hubs.findFirst({ - where: { name }, - include: { connections: true }, - }); - - if (!hubExists) { - return await interaction.reply({ - content: `${emojis.normal.no} Unable to find a hub with that name!`, - ephemeral: true, - }); - } - - const guildInHub = hubExists.connections.find(c => c.serverId === channel.guildId); - if (guildInHub) { - return await interaction.reply({ - content: `This server has already joined hub **${hubExists?.name}** from <#${guildInHub.channelId}>! Please leave the hub from that channel first, or change the channel using \`/network manage\`.`, - ephemeral: true, - }); - } - - // the only way to join a private hub is through it's invite code - if (hubExists?.private && !invite) { - return await interaction.reply({ - content: `${emojis.normal.no} Unable to find a hub with that name!`, - ephemeral: true, - }); - } - } - - if (!hubExists) return interaction.reply({ content: 'An error occured.', ephemeral: true }); - - - const serverInBlacklist = await fetchServerBlacklist(hubExists.id, channel.guildId); - if (serverInBlacklist) { - await interaction.reply('This server is blacklisted from joining this hub.'); - return; - } - - const userInBlacklist = await fetchUserBlacklist(hubExists.id, interaction.user.id); - if (userInBlacklist) { - await interaction.reply('You have been blacklisted from joining this hub.'); - return; - } - - if (!await onboarding.execute(interaction, hubExists.name, channel.id)) return interaction.deleteReply().catch(() => null); - - const created = await createConnection(interaction.guild, hubExists, channel).catch(() => { - interaction.reply({ - content: `${emojis.normal.no} An error occured while connecting this channel to the hub! Please make sure I have the [required permissions](https://discord-interchat.github.io/docs/#adding-interchat-to-your-server) and try again.`, - ephemeral: true, - }); - return null; - }); - - if (created) await displaySettings.execute(interaction, created.channelId); - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/joined.ts b/src/Scripts/hub/joined.ts deleted file mode 100644 index 2cb08468..00000000 --- a/src/Scripts/hub/joined.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import { paginate } from '../../Utils/paginator'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - await interaction.deferReply(); - - const db = getDb(); - const connections = await db.connectedList.findMany({ - where: { serverId: interaction.guild?.id }, - include: { hub: true }, - }); - if (connections.length === 0) { - return interaction.editReply(`${emojis.normal.no} You have not joined any hubs yet!`); - } - - const allFields = connections.map((con) => ({ - name: `${con.hub?.name}`, - value: `<#${con.channelId}>`, - inline: true, - })); - - if (allFields.length > 25) { - const paginateEmbeds: EmbedBuilder[] = []; - let currentEmbed: EmbedBuilder | undefined; - - // Split the fields into multiple embeds - allFields.forEach((field, index) => { - if (index % 25 === 0) { - // Start a new embed - currentEmbed = new EmbedBuilder() - .setTitle('Joined hubs') - .setDescription(`This server is a part of **${connections.length}** hub(s).`) - .setColor('Blue') - .setFooter({ - text: 'Use /hub leave to leave a hub.', - }); - paginateEmbeds.push(currentEmbed); - } - - // Add the field to the current embed - if (currentEmbed) { - currentEmbed.addFields(field); - } - }); - - paginate(interaction, paginateEmbeds); - return; - } - - const embed = new EmbedBuilder() - .setTitle('Joined hubs') - .setDescription(`This server is a part of **${connections.length}** hub(s).`) - .setFields(allFields) - .setColor('Blue') - .setFooter({ - text: 'Use /hub leave to leave a hub.', - }); - await interaction.editReply({ embeds: [embed] }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/leave.ts b/src/Scripts/hub/leave.ts deleted file mode 100644 index 233a919e..00000000 --- a/src/Scripts/hub/leave.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, ComponentType, EmbedBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction, channelId: string) { - const { normal } = emojis; - const db = getDb(); - - if (!await db.connectedList.findFirst({ where: { channelId } })) { - return await interaction.reply(`${normal.no} The channel ${channelId} does not have any networks.`); - } - - const choiceButtons = new ActionRowBuilder().addComponents([ - new ButtonBuilder().setCustomId('yes').setLabel('Yes').setStyle(ButtonStyle.Success), - new ButtonBuilder().setCustomId('no').setLabel('No').setStyle(ButtonStyle.Danger), - ]); - - const resetConfirmEmbed = new EmbedBuilder() - .setTitle('Delete Network Connection') - .setDescription('Are you sure? You will have to rejoin the hub to use the network again! All previous connection data will be lost.') - .setColor('Red') - .setFooter({ text: 'Confirm within the next 10 seconds.' }); - - const resetConfirmMsg = await interaction.reply({ - embeds: [resetConfirmEmbed], - components: [choiceButtons], - fetchReply: true, - }); - - const resetCollector = resetConfirmMsg.createMessageComponentCollector({ - filter: (m) => m.user.id == interaction.user.id, - componentType: ComponentType.Button, - idle: 10_000, - max: 1, - }); - - // Creating collector for yes/no button - resetCollector.on('collect', async (collected) => { - if (collected.customId === 'no') { - await interaction.deleteReply(); - return; - } - - await db.connectedList.delete({ where: { channelId } }); - await collected.update({ - content: `${normal.yes} Deleted network connection from <#${channelId}> and left the hub!`, - embeds: [], - components: [], - }).catch(() => null); - }); - - resetCollector.on('end', () => { - resetConfirmMsg.delete().catch(() => null); - }); - }, -}; diff --git a/src/Scripts/hub/manage.ts b/src/Scripts/hub/manage.ts deleted file mode 100644 index e394da84..00000000 --- a/src/Scripts/hub/manage.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { connectedList, hubs } from '@prisma/client'; -import { captureException } from '@sentry/node'; -import { logger } from '@sentry/utils'; -import { ActionRowBuilder, ChatInputCommandInteraction, ComponentType, EmbedBuilder, ModalBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import { stripIndents } from 'common-tags'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - await interaction.deferReply(); - - const db = getDb(); - const chosenHub = interaction.options.getString('name', true); - let hubInDb = await db.hubs.findFirst({ - where: { - name: chosenHub, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, - ], - }, - include: { connections: true }, - }); - - if (!hubInDb) { - await interaction.followUp(emojis.normal.no + ' Hub not found.'); - return; - } - - const actionsSelect = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('hub_actions') - .addOptions([ - { - label: 'Edit Description', - value: 'description', - description: 'Edit the hub description.', - emoji: '✏️', - }, - { - label: 'Toggle Visibility', - value: 'visibility', - description: 'Toggle the hub visibility between public and private.', - emoji: '🔒', - }, - { - label: 'Set Icon', - value: 'icon', - description: 'Set the hub icon.', - emoji: '🖼️', - }, - { - label: 'Set Banner', - value: 'banner', - description: 'Set the hub banner.', - emoji: '🎨', - }, - ]), - ); - - - const hubEmbed = async (hub: hubs & { connections: connectedList[] }) => { - const hubBlacklistedUsers = await db.blacklistedUsers.count({ - where: { hubs: { some: { hubId: hub.id } } }, - }); - const hubBlacklistedServers = await db.blacklistedServers.count({ - where: { hubs: { some: { hubId: hub.id } } }, - }); - return new EmbedBuilder() - .setTitle(hub.name) - .setColor('Random') - .setDescription(stripIndents` - ${hub.description} - - __**Public:**__ ${hub.private ? emojis.normal.no : emojis.normal.yes} - `) - .setThumbnail(hub.iconUrl) - .setImage(hub.bannerUrl) - .addFields( - { - name: 'Blacklists', - value: stripIndents` - - Users: ${hubBlacklistedUsers} - - Servers: ${hubBlacklistedServers} - `, - inline: true, - }, - - { - name: 'Hub Stats', - value: stripIndents` - - Moderators: ${hub.moderators.length.toString()} - - Connected: ${hub.connections.length} - - Owner: <@${hub.ownerId}> - `, - inline: true, - }, - ); - - }; - - const reply = await interaction.followUp({ - embeds: [await hubEmbed(hubInDb)], - components: [actionsSelect], - }); - - const collector = reply.createMessageComponentCollector({ - filter: (i) => i.user.id === interaction.user.id, - idle: 60_000 * 5, - componentType: ComponentType.StringSelect, - }); - - collector.on('collect', async (i) => { - if (i.customId !== 'hub_actions') return; - - hubInDb = await db.hubs.findFirst({ - where: { id: hubInDb?.id }, - include: { connections: true }, - }); - - if (!hubInDb) { - await i.reply({ content: 'This hub no longer exists!', ephemeral: true }); - return; - } - - switch (i.values[0]) { - case 'icon': { - const modal = new ModalBuilder() - .setCustomId(i.id) - .setTitle('Change Hub Icon') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setLabel('Enter Icon URL') - .setPlaceholder('Enter a valid imgur image URL.') - .setStyle(TextInputStyle.Short) - .setCustomId('icon'), - )); - - await i.showModal(modal); - - const modalResponse = await i.awaitModalSubmit({ - filter: m => m.customId === modal.data.custom_id, - time: 60_000 * 5, - }).catch(e => { - if (!e.message.includes('ending with reason: time')) { - logger.error(e); - captureException(e, { - user: { id: i.user.id, username: i.user.username }, - extra: { context: 'This happened when user tried to change hub icon.' }, - }); - } - return null; - }); - - if (!modalResponse) return; - - const newIcon = modalResponse.fields.getTextInputValue('icon'); - // check if icon is a valid imgur link - const imgurLink = newIcon.match(/\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g); - if (!imgurLink) { - await modalResponse.reply({ - content: 'Invalid icon URL. Please make sure it is a valid imgur image URL.', - ephemeral: true, - }); - return; - } - - await db.hubs.update({ - where: { id: hubInDb?.id }, - data: { iconUrl: imgurLink[0] }, - }); - - await modalResponse.reply({ - content: 'Successfully updated icon!', - ephemeral: true, - }); - break; - } - - case 'description': { - const modal = new ModalBuilder() - .setCustomId(i.id) - .setTitle('Edit Hub Description') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setLabel('Enter Description') - .setPlaceholder('A detailed description about the hub.') - .setMaxLength(1024) - .setStyle(TextInputStyle.Paragraph) - .setCustomId('description'), - ), - ); - - await i.showModal(modal); - - const modalResponse = await i.awaitModalSubmit({ - filter: m => m.customId === modal.data.custom_id, - time: 60_000 * 5, - }).catch(e => { - if (!e.message.includes('ending with reason: time')) { - logger.error(e); - captureException(e, { - user: { id: i.user.id, username: i.user.username }, - extra: { context: 'This happened when user tried to edit hub desc.' }, - }); - } - return null; - }); - - if (!modalResponse) return; - - const description = modalResponse.fields.getTextInputValue('description'); - await db.hubs.update({ - where: { id: hubInDb?.id }, - data: { description }, - }); - - await modalResponse.reply({ - content: 'Successfully updated hub description.', - ephemeral: true, - }); - break; - } - - case 'banner': { - const modal = new ModalBuilder() - .setCustomId(i.id) - .setTitle('Set Hub Banner') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setLabel('Enter Banner URL') - .setPlaceholder('Enter a valid imgur image URL.') - .setStyle(TextInputStyle.Short) - .setCustomId('banner'), - )); - - await i.showModal(modal); - - const modalResponse = await i.awaitModalSubmit({ - filter: m => m.customId === modal.data.custom_id, - time: 60_000 * 5, - }).catch(e => { - if (!e.message.includes('ending with reason: time')) { - logger.error(e); - captureException(e, { - user: { id: i.user.id, username: i.user.username }, - extra: { context: 'Occured during hub banner change.' }, - }); - } - return null; - }); - - if (!modalResponse) return; - - const newBanner = modalResponse.fields.getTextInputValue('banner'); - // check if banner is a valid imgur link - const imgurLink = newBanner.match(/\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g); - if (!imgurLink) { - await modalResponse.reply({ - content: 'Invalid banner URL. Please make sure it is a valid imgur image URL.', - ephemeral: true, - }); - return; - } - - await db.hubs.update({ - where: { id: hubInDb?.id }, - data: { bannerUrl: imgurLink[0] }, - }); - - await modalResponse.reply({ - content: 'Successfully updated banner!', - ephemeral: true, - }); - break; - } - - case 'visibility': { - await db.hubs.update({ - where: { id: hubInDb?.id }, - data: { private: !hubInDb?.private }, - }); - await i.reply({ - content: `Successfully set hub visibility to **${hubInDb?.private ? 'Public' : 'Private'}**.`, - ephemeral: true, - }); - break; - } - - default: - break; - } - - hubInDb = await db.hubs.findFirst({ - where: { id: hubInDb?.id }, - include: { connections: true }, - }); - if (hubInDb) { - await interaction.editReply({ embeds: [await hubEmbed(hubInDb)] }).catch(() => null); - } - }); - - collector.on('end', async () => { - actionsSelect.components[0].setDisabled(true); - await interaction.editReply({ - components: [actionsSelect], - }).catch(() => null); - }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/moderator.ts b/src/Scripts/hub/moderator.ts deleted file mode 100644 index e3442ee9..00000000 --- a/src/Scripts/hub/moderator.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const db = getDb(); - const hubName = interaction.options.getString('hub', true); - const hub = await db.hubs.findFirst({ - where: { - name: hubName, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, - ], - }, - }); - - if (!hub) { - return await interaction.reply({ - content: 'Invalid hub input. Make sure the hub exists and that you are a owner/manager of the hub.', - ephemeral: true, - }); - } - - switch (interaction.options.getSubcommand()) { - case 'add': { - const user = interaction.options.getUser('user', true); - - if (hub.moderators.find((mod) => mod.userId === user.id)) { - return interaction.reply({ - content: `User ${user} is already a moderator for **${hub.name}**!`, - ephemeral: true, - }); - } - - const position = interaction.options.getString('role') ?? 'network_mod'; - await db.hubs.update({ - where: { id: hub.id }, - data: { moderators: { push: { userId: user.id, position } } }, - }); - interaction.reply(`Added ${user} as a hub moderator for **${hub.name}**!`); - break; - } - - case 'remove': { - const user = interaction.options.getUser('user', true); - - if (!hub.moderators.find((mod) => mod.userId === user.id)) { - return interaction.reply({ - content: `User ${user} is not a moderator for **${hub.name}**!`, - ephemeral: true, - }); - } - - if (hub.ownerId !== interaction.user.id) { - if (user.id === interaction.user.id) { - return interaction.reply({ - content: 'I don\'t know why you would want to do that, but only the owner of the hub can remove you!', - ephemeral: true, - }); - } - - if (hub.moderators.find(m => m.position === 'manager' && m.userId === user.id)) { - return interaction.reply({ - content: 'Only the owner of the hub can remove a manager!', - ephemeral: true, - }); - } - } - await db.hubs.update({ - where: { id: hub.id }, - data: { - moderators: { deleteMany: { where: { userId: user.id } } }, - }, - }); - interaction.reply(`Removed hub moderator ${user} from **${hub.name}**!`); - break; - } - - case 'update': { - const user = interaction.options.getUser('user', true); - const position = interaction.options.getString('role', true); - - if (!hub.moderators.find((mod) => mod.userId === user.id)) { - return interaction.reply({ - content: `User ${user} is not a moderator for **${hub.name}**!`, - ephemeral: true, - }); - } - - if (hub.ownerId !== interaction.user.id && user.id === interaction.user.id) { - return interaction.reply({ - content: 'Only the owner of the hub can update your role!', - ephemeral: true, - }); - } - - await db.hubs.update({ - where: { id: hub.id }, - data: { - moderators: { - updateMany: { where: { userId: user.id }, data: { position } }, - }, - }, - }); - interaction.reply(`Sucessfully moved ${user} to the role of \`${position}\` for **${hub.name}**!`); - break; - } - - case 'list': { - await interaction.reply({ - embeds: [ - new EmbedBuilder() - .setTitle('Hub Moderators') - .setDescription( - hub.moderators.length > 0 - ? hub.moderators - .map((mod, index) => `${index + 1}. <@${mod.userId}> - ${mod.position === 'network_mod' ? 'Network Moderator' : 'Hub Manager'}`) - .join('\n') - : 'There are no moderators for this hub yet.', - ) - .setColor('Aqua') - .setTimestamp(), - ], - ephemeral: true, - }); - break; - } - default: - break; - } - }, -}; \ No newline at end of file diff --git a/src/Scripts/hub/settings.ts b/src/Scripts/hub/settings.ts deleted file mode 100644 index f0b0fff5..00000000 --- a/src/Scripts/hub/settings.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { ActionRowBuilder, ChatInputCommandInteraction, ComponentType, EmbedBuilder, StringSelectMenuBuilder } from 'discord.js'; -import { constants, getDb } from '../../Utils/utils'; -import { hubs } from '@prisma/client'; -import { HubSettingsBitField, HubSettingsString } from '../../Utils/hubSettingsBitfield'; -import emojis from '../../Utils/JSON/emoji.json'; - -const genSettingsEmbed = (hub: hubs, yesEmoji: string, noEmoji: string) => { - const settings = new HubSettingsBitField(hub.settings); - const settingDescriptions = { - HideLinks: '**Hide Links** - Redact links sent by users.', - Reactions: '**Reactions** - Allow users to react to messages.', - BlockInvites: '**Block Invites** - Prevent users from sending Discord invites.', - SpamFilter: '**Spam Filter** - Automatically blacklist spammers for 5 minutes.', - UseNicknames: '**Use Nicknames** - Use server nicknames as the network usernames.', - }; - - return new EmbedBuilder() - .setAuthor({ name: `${hub.name} Settings`, iconURL: hub.iconUrl }) - .setDescription(Object.entries(settingDescriptions).map(([key, value]) => { - const flag = settings.has(key as HubSettingsString); - return `- ${flag ? yesEmoji : noEmoji} ${value}`; - }).join('\n')) - .setFooter({ text: 'Use the select menu below to toggle.' }) - .setColor(constants.colors.interchatBlue) - .setTimestamp(); -}; - -const genSelectMenu = ( - hubSettings: HubSettingsBitField, - disabledEmote: string, - enabledEmote: string, -) => { - return new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('hub_settings') - .setPlaceholder('Select an option') - .addOptions( - Object.keys(HubSettingsBitField.Flags).map((key) => { - const flag = hubSettings.has(key as HubSettingsString); - const emoji = flag ? disabledEmote : enabledEmote; - return { - label: `${flag ? 'Disable' : 'Enable'} ${key}`, - value: key, - emoji, - }; - }), - ), - ); -}; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const hubName = interaction.options.getString('hub', true); - - const db = getDb(); - let hub = await db.hubs.findUnique({ - where: { - name: hubName, - OR: [ - { - moderators: { some: { userId: interaction.user.id, position: 'manager' } }, - }, - { ownerId: interaction.user.id }, - ], - }, - }); - - if (!hub) { - return interaction.reply({ - content: 'Hub not found.', - ephemeral: true, - }); - } - - const hubSettings = new HubSettingsBitField(hub.settings); - const embed = genSettingsEmbed(hub, emojis.normal.enabled, emojis.normal.disabled); - const selects = genSelectMenu(hubSettings, emojis.normal.disabled, emojis.normal.enabled); - - const initReply = await interaction.reply({ embeds: [embed], components: [selects] }); - - const collector = initReply.createMessageComponentCollector({ - time: 60 * 1000, - filter: (i) => i.user.id === interaction.user.id && i.customId === 'hub_settings', - componentType: ComponentType.StringSelect, - }); - - // respond to select menu - collector.on('collect', async (i) => { - const selected = i.values[0] as HubSettingsString; - - hub = await db.hubs.update({ - where: { name: hub?.name }, - data: { settings: hubSettings.toggle(selected).bitfield }, - }); - - const newEmbed = genSettingsEmbed(hub, emojis.normal.enabled, emojis.normal.disabled); - const newSelects = genSelectMenu(hubSettings, emojis.normal.disabled, emojis.normal.enabled); - - await i.update({ - embeds: [newEmbed], - components: [newSelects], - }); - }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/message/addBadges.ts b/src/Scripts/message/addBadges.ts deleted file mode 100644 index 53431c49..00000000 --- a/src/Scripts/message/addBadges.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PrismaClient } from '@prisma/client'; -import { EmbedBuilder, Message } from 'discord.js'; -import { badgeToEmoji } from '../../Utils/utils'; - -export default { - async execute(message: Message, database: PrismaClient, embed: EmbedBuilder, censoredEmbed: EmbedBuilder) { - const badges = await database.userBadges.findFirst({ where: { userId: message.author.id } }); - - if (badges && badges.badges.length > 0) { - const badgeString = badgeToEmoji(badges.badges).join(' '); - - embed.setTitle(badgeString); - censoredEmbed.setTitle(badgeString); - } - }, -}; diff --git a/src/Scripts/message/antispam.ts b/src/Scripts/message/antispam.ts deleted file mode 100644 index 15e8c9ea..00000000 --- a/src/Scripts/message/antispam.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { User, Collection } from 'discord.js'; -import { cancelJob, scheduleJob } from 'node-schedule'; -interface UserOpts { - timestamps: number[]; - infractions: number; -} - -const userCol = new Collection(); -const WINDOW_SIZE = 5000; -const MAX_STORE = 3; - -export default { - execute(author: User, maxInfractions = MAX_STORE) { - const userInCol = userCol.get(author.id); - const currentTimestamp = Date.now(); - - if (userInCol) { - if (userInCol.infractions >= maxInfractions) { - // resetting count as it is assumed they will be blacklisted right after - userCol.delete(author.id); - return userInCol; - } - - const { timestamps } = userInCol; - - if (timestamps.length === MAX_STORE) { - // Check if all the timestamps are within the window - const oldestTimestamp = timestamps[0]; - const isWithinWindow = currentTimestamp - oldestTimestamp <= WINDOW_SIZE; - - userCol.set(author.id, { - timestamps: [...timestamps.slice(1), currentTimestamp], - infractions: isWithinWindow ? userInCol.infractions + 1 : userInCol.infractions, - }); - setSpamTimers(author.id); - if (isWithinWindow) return userInCol; - } - - else { - userCol.set(author.id, { - timestamps: [...timestamps, currentTimestamp], - infractions: userInCol.infractions, - }); - } - } - - else { - userCol.set(author.id, { - timestamps: [currentTimestamp], - infractions: 0, - }); - setSpamTimers(author.id); - } - }, -}; - -export function setSpamTimers(userId: string): void { - const five_min = 60 * 5000; - const userInCol = userCol.get(userId); - const lastMsgTimestamp = userInCol?.timestamps[userInCol.timestamps.length - 1]; - - if (userInCol && lastMsgTimestamp && Date.now() - five_min <= lastMsgTimestamp) { - cancelJob(`removeFromCol_${userId}`); - } - - scheduleJob(`removeFromCol_${userId}`, new Date(Date.now() + five_min), () => { - userCol.delete(userId); - }); -} \ No newline at end of file diff --git a/src/Scripts/message/checks.ts b/src/Scripts/message/checks.ts deleted file mode 100644 index c16df490..00000000 --- a/src/Scripts/message/checks.ts +++ /dev/null @@ -1,87 +0,0 @@ -import wordFilter from '../../Utils/wordFilter'; -import antiSpam from './antispam'; -import emojis from '../../Utils/JSON/emoji.json'; -import { Message } from 'discord.js'; -import { slurs } from '../../Utils/JSON/badwords.json'; -import { replaceLinks } from '../../Utils/utils'; -import { connectedList } from '@prisma/client'; -import { HubSettingsBitField } from '../../Utils/hubSettingsBitfield'; -import { addUserBlacklist, fetchServerBlacklist, fetchUserBlacklist, notifyBlacklist, scheduleUnblacklist } from '../../Utils/blacklist'; -export default { - async execute(message: Message, networkData: connectedList, settings: HubSettingsBitField) { - // true = pass, false = fail (checks) - - const userBlacklisted = await fetchUserBlacklist(networkData.hubId, message.author.id); - const serverBlacklisted = await fetchServerBlacklist(networkData.hubId, message.guildId || ''); - if (userBlacklisted || serverBlacklisted) return false; - - if (settings.has('SpamFilter')) { - const antiSpamResult = antiSpam.execute(message.author, 3); - if (antiSpamResult) { - if (antiSpamResult.infractions >= 3) { - await addUserBlacklist(networkData.hubId, message.client.user, message.author, 'Auto-blacklisted for spamming.', 60 * 5000); - scheduleUnblacklist('user', message.client, message.author.id, networkData.hubId, 60 * 5000); - - notifyBlacklist(message.author, networkData.hubId, new Date(Date.now() + 60 * 5000), 'Auto-blacklisted for spamming.').catch(() => null); - } - message.react(emojis.icons.timeout).catch(() => null); - return false; - } - - if (message.content.length > 1000) { - message.reply('Please keep your message shorter than 1000 characters long.'); - return false; - } - } - - // check if message contains slurs - if (slurs.some((slur) => message.content.toLowerCase().includes(slur))) { - wordFilter.log(message.content, message.author, message.guildId, networkData.hubId); - return false; - } - - if ( - settings.has('BlockInvites') && - message.content.includes('discord.gg') || - message.content.includes('discord.com/invite') || - message.content.includes('dsc.gg') - ) { - message.reply('Do not advertise or promote servers in the network. Set an invite in `/network manage` instead!'); - return false; - } - - if (message.stickers.size > 0 && !message.content) { - message.reply('Sending stickers in the network is not possible due to discord\'s limitations.'); - return false; - } - - // TODO allow multiple attachments when embeds can have multiple images - const attachment = message.attachments.first(); - const allowedTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/jpg', 'image/webp']; - - if (attachment?.contentType && !allowedTypes.includes(attachment.contentType)) { - message.reply('Only images and gifs are allowed to be sent within the network.'); - return false; - } - - if (attachment && attachment.size > 1024 * 1024 * 8) { - message.reply('Please keep your attachments under 8MB.'); - return false; - } - - // dont send message if guild name is inappropriate - if (wordFilter.check(message.guild?.name)) { - message.channel.send('I have detected words in the server name that are potentially offensive, Please fix it before using this chat!'); - return false; - } - - if (wordFilter.check(message.content)) wordFilter.log(message.content, message.author, message.guildId, networkData.hubId); - - const urlRegex = /https?:\/\/(?!tenor\.com|giphy\.com)\S+/g; - if (settings.has('HideLinks') && message.content.match(urlRegex)) { - message.content = replaceLinks(message.content); - } - - return true; - }, -}; diff --git a/src/Scripts/message/cleanup.ts b/src/Scripts/message/cleanup.ts deleted file mode 100644 index 9a0355d3..00000000 --- a/src/Scripts/message/cleanup.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Message } from 'discord.js'; -import { NetworkWebhookSendResult } from '../../Events/messageCreate'; -import { getDb } from '../../Utils/utils'; - -/** - * Disconnects connections if an errored occured while sending the message to it. - * Otherwise, inserts messages into `messageData` collection for future use. - */ -export default { - async execute(message: Message, channelAndMessageIds: NetworkWebhookSendResult[], hubId: string | null) { - const messageDataObj: { channelId: string, messageId: string }[] = []; - const invalidWebhookURLs: string[] = []; - - channelAndMessageIds.forEach((result) => { - if (typeof result.messageOrError === 'string') { - if ( - result.messageOrError.includes('Invalid Webhook Token') || - result.messageOrError.includes('Unknown Webhook') - ) invalidWebhookURLs.push(result.webhookURL); - } - else { - messageDataObj.push({ - channelId: result.messageOrError.channel_id, - messageId: result.messageOrError.id, - }); - } - }); - - const db = getDb(); - if (message.guild && hubId) { - // store message data in db - await db.messageData.create({ - data: { - hub: { connect: { id: hubId } }, - channelAndMessageIds: messageDataObj, - timestamp: message.createdAt, - authorId: message.author.id, - serverId: message.guild.id, - reference: message.reference, - reactions: {}, - }, - }); - } - - // disconnect network if, webhook does not exist/bot cannot access webhook - if (invalidWebhookURLs.length > 0) { - await db.connectedList.updateMany({ - where: { webhookURL: { in: invalidWebhookURLs } }, - data: { connected: false }, - }); - } - }, -}; \ No newline at end of file diff --git a/src/Scripts/message/messageContentModifiers.ts b/src/Scripts/message/messageContentModifiers.ts deleted file mode 100644 index de5980f0..00000000 --- a/src/Scripts/message/messageContentModifiers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Message } from 'discord.js'; -import { NetworkMessage } from '../../Events/messageCreate'; - -export default { - getReferredContent(referredMessage: Message) { - let referredContent = referredMessage.content || referredMessage.embeds[0]?.description; - - if (!referredContent) { - referredContent = '*Original message contains attachment <:attachment:1102464803647275028>*'; - } - else if (referredContent.length > 1000) {referredContent = referredContent.slice(0, 1000) + '...';} - - return referredContent; - }, - - async getAttachmentURL(message: NetworkMessage) { - // Tenor Gifs / Image URLs - const imageURLRegex = /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.gif|\.png|\.webp)/; - const URLMatch = message.content.match(imageURLRegex); - - if (URLMatch) return URLMatch[0]; - - const tenorRegex = /https:\/\/tenor\.com\/view\/.*-(\d+)/; - const gifMatch = message.content.match(tenorRegex); - - if (gifMatch) { - if (!process.env.TENOR_KEY) throw new TypeError('Tenor API key not found in .env file.'); - - const n = gifMatch[0].split('-'); - const id = n.at(-1); - const api = `https://g.tenor.com/v1/gifs?ids=${id}&key=${process.env.TENOR_KEY}`; - const gifJSON = await (await fetch(api)).json(); - - return gifJSON.results[0].media[0].gif.url as string; - } - return null; - }, -}; diff --git a/src/Scripts/network/displaySettings.ts b/src/Scripts/network/displaySettings.ts deleted file mode 100644 index 29a69293..00000000 --- a/src/Scripts/network/displaySettings.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { ChatInputCommandInteraction, ButtonBuilder, ActionRowBuilder, ButtonStyle, GuildTextBasedChannel, EmbedBuilder, ChannelType, ComponentType, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, Interaction, ChannelSelectMenuBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, TextChannel, ButtonInteraction, AnySelectMenuInteraction, Webhook, ThreadChannel } from 'discord.js'; -import { reconnect, disconnect } from '../../Utils/network'; -import { constants, getDb, yesOrNoEmoji } from '../../Utils/utils'; -import { captureException } from '@sentry/node'; -import emojis from '../../Utils/JSON/emoji.json'; -import logger from '../../Utils/logger'; - -function updateConnectionButtons(connected: boolean | undefined, disconnectEmoji: string, connectEmoji: string) { - return new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId('toggle_connection') - .setLabel(connected ? 'Disconnect' : 'Reconnect') - .setStyle(connected ? ButtonStyle.Danger : ButtonStyle.Success) - .setEmoji(connected ? disconnectEmoji : connectEmoji), - ]); -} - -// function to make it easier to edit embeds with updated data -async function setupEmbed(interaction: Interaction, channelId: string) { - const networkData = await getDb().connectedList.findFirst({ where: { channelId }, include: { hub: true } }); - - const { yes, no, enabled, disabled } = emojis.normal; - const invite = networkData?.invite - ? `Code: [\`${networkData.invite}\`](https://discord.gg/${networkData.invite})` - : 'Not Set.'; - - return new EmbedBuilder() - .setTitle('Edit Settings') - .setDescription(`Showing network settings for <#${channelId}>.`) - .addFields([ - { name: 'Channel', value: `<#${channelId}>`, inline: true }, - { name: 'Hub', value: `${networkData?.hub?.name}`, inline: true }, - { name: 'Invite', value: invite, inline: true }, - { name: 'Connected', value: yesOrNoEmoji(networkData?.connected, yes, no), inline: true }, - { name: 'Compact', value: yesOrNoEmoji(networkData?.compact, enabled, disabled), inline: true }, - { name: 'Profanity Filter', value: yesOrNoEmoji(networkData?.profFilter, enabled, disabled), inline: true }, - { name: 'Embed Color', value: networkData?.embedColor ? `\`${networkData?.embedColor}\`` : no, inline: true }, - ]) - .setColor(constants.colors.interchatBlue) - .setThumbnail(interaction.guild?.iconURL() || interaction.client.user.avatarURL()) - .setTimestamp() - .setFooter({ text: 'Use to menu below to edit.' }); -} - -export default { - async execute(interaction: ChatInputCommandInteraction | ButtonInteraction | AnySelectMenuInteraction, channelId: string, connected?: boolean) { - if (!interaction.deferred && !interaction.replied) await interaction.deferReply(); - - const db = getDb(); - const emoji = emojis; - - const customizeMenu = new ActionRowBuilder().addComponents([ - new StringSelectMenuBuilder() - .setCustomId('customize') - .setPlaceholder('🛠️ Select a setting to toggle') - .addOptions( - new StringSelectMenuOptionBuilder() - .setLabel('Compact') - .setEmoji(emoji.normal.clipart) - .setDescription('Disable embeds in the network to fit more messages. Works with webhooks.') - .setValue('compact'), - new StringSelectMenuOptionBuilder() - .setLabel('Profanity Filter') - .setEmoji('🤬') - .setDescription('Toggle swear word censoring for this server.') - .setValue('profanity'), - new StringSelectMenuOptionBuilder() - .setLabel('Invite Link') - .setEmoji(emoji.icons.members) - .setDescription('Set an invite for network users to join your server easily!') - .setValue('invite'), - new StringSelectMenuOptionBuilder() - .setLabel('Switch Channel') - .setEmoji(emoji.icons.store) - .setDescription('Set a different channel for the network.') - .setValue('change_channel'), - new StringSelectMenuOptionBuilder() - .setLabel('Embed Color') - .setEmoji('🎨') - .setDescription('Set the color of the embeds sent in the network.') - .setValue('embed_color'), - ), - ]); - - const channelExists = await interaction.client.channels.fetch(channelId).catch(() => null); - const setupMessage = await interaction.editReply({ - embeds: [await setupEmbed(interaction, channelId)], - components: [customizeMenu, updateConnectionButtons(connected, emoji.icons.disconnect, emoji.icons.connect)], - }); - - if (!channelExists) { - await disconnect(channelId); - await interaction.followUp({ - content: `${emoji.normal.no} Automatically disconnected from network due to errors. Change the channel to use the network.`, - ephemeral: true, - }); - } - - const filter = (m: Interaction) => m.user.id === interaction.user.id; - - const buttonCollector = setupMessage.createMessageComponentCollector({ - filter, - componentType: ComponentType.Button, - }); - - /* ------------------- Button Responce collectors ---------------------- */ - buttonCollector.on('collect', async (component) => { - const updConnection = await db.connectedList.findFirst({ where: { channelId } }); - if (!updConnection) { - await component.reply({ - content: `${emoji.normal.no} This network no longer exists!`, - ephemeral: true, - }); - return; - } - - switch (component.customId) { - case 'toggle_connection': { - const channel = await interaction.guild?.channels - .fetch(String(updConnection.channelId)) - .catch(() => null) as GuildTextBasedChannel | null; - - if (!channel) { - component.reply({ - content: `${emoji.normal.no} Unable to find network channel!`, - ephemeral: true, - }); - return; - } - - updConnection.connected ? await disconnect(updConnection.channelId) : await reconnect(channel.id); - - await component.reply({ - content: updConnection.connected - ? `Disconnected <#${updConnection.channelId}> from the hub!` - : `Reconnected <#${updConnection.channelId}> to the hub!`, - ephemeral: true, - }); - interaction.editReply({ - components: [customizeMenu, updateConnectionButtons(!updConnection.connected, emoji.icons.disconnect, emoji.icons.connect), - ], - }); - break; - } - - default: - break; - } - component.replied || component.deferred - ? interaction.editReply({ embeds: [await setupEmbed(interaction, updConnection.channelId)] }) - : component.update({ embeds: [await setupEmbed(interaction, updConnection.channelId)] }); - - }); - - - /* ------------------- Replying to SelectMenus ---------------------- */ - const selectCollector = setupMessage.createMessageComponentCollector({ - filter, - idle: 60_000 * 5, - componentType: ComponentType.StringSelect, - }); - - selectCollector.on('collect', async (settingsMenu) => { - const updConnection = await db.connectedList.findFirst({ where: { channelId } }); - if (!updConnection) { - await settingsMenu.reply({ - content: `${emoji.normal.no} This network no longer exists!`, - ephemeral: true, - }); - return; - } - - switch (settingsMenu.values[0]) { - /* Embed color selection */ - case 'embed_color': { - const modal = new ModalBuilder() - .setTitle('Set Embed Color') - .setCustomId(settingsMenu.id) - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('embed_color') - .setStyle(TextInputStyle.Short) - .setLabel('Embed Color') - .setPlaceholder('Provide a hex color code or leave blank to remove.') - .setValue(updConnection.embedColor || '#000000') - .setRequired(false), - ), - ); - - await settingsMenu.showModal(modal); - - const modalSubmit = await settingsMenu.awaitModalSubmit({ - time: 60_000, - filter: (i) => i.customId === modal.data.custom_id, - }).catch((e) => { - if (!e.message.includes('reason: time')) { - logger.error(e); - captureException(e); - } - return null; - }); - - if (!modalSubmit) return; - - - const embedColor = modalSubmit.fields.getTextInputValue('embed_color'); - - const hex_regex = /^#[0-9A-F]{6}$/i; - if (embedColor && !hex_regex.test(embedColor)) { - modalSubmit.reply({ - content: `${emoji.normal.no} Invalid hex color code. Please try again.`, - ephemeral: true, - }); - return; - } - - await db.connectedList.update({ - where: { channelId: updConnection.channelId }, - data: { embedColor: embedColor ? embedColor : { unset: true } }, - }); - - modalSubmit.reply({ - content: `${emoji.normal.yes} Embed color successfully ${embedColor ? `set to \`${embedColor}\`!` : 'unset'}`, - ephemeral: true, - }); - break; - } - - /* Compact / Normal mode toggle */ - case 'compact': { - await db.connectedList.update({ - where: { channelId: updConnection.channelId }, - data: { compact: !updConnection.compact }, - }); - break; - } - /* Profanity toggle */ - case 'profanity': { - await db.connectedList.update({ - where: { channelId: updConnection.channelId }, - data: { profFilter: !updConnection.profFilter }, - }); - break; - } - - case 'change_channel': { - const channelMenu = new ActionRowBuilder() - .addComponents( - new ChannelSelectMenuBuilder() - .setCustomId('newChannelSelect') - .setPlaceholder('Select new channel') - .addChannelTypes(ChannelType.GuildText, ChannelType.PublicThread, ChannelType.PrivateThread), - ); - - const changeMsg = await settingsMenu.reply({ - content: 'Please select a channel within the next 20 seconds.', - components: [channelMenu], - ephemeral: true, - fetchReply: true, - }); - - const selected = await changeMsg.awaitMessageComponent({ - componentType: ComponentType.ChannelSelect, - time: 20_000, - }).catch(() => null); - - if (!selected) return; - - const newchannel = selected.guild?.channels.cache.get(selected?.values[0]) as TextChannel | ThreadChannel; - const newchannelInDb = await db.connectedList.findFirst({ where: { channelId: newchannel.id } }); - - // if the hubId doesn't match with the already connected channel - // don't let to switch channel as it is already connected to another hub - if (newchannelInDb && newchannelInDb.channelId !== updConnection.channelId) { - await selected.update({ - content: `${emoji.normal.no} Channel ${newchannel} has already joined a hub. Either leave that hub first or select another channel.`, - components: [], - }); - return; - } - - let webhook: Webhook | null = null; - if (newchannel.type === ChannelType.GuildText) { - const webhooks = await newchannel.fetchWebhooks(); - const interchatHook = webhooks?.find((hook) => hook.owner?.id === hook.client.user?.id); - - // create a webhook in the new channel - webhook = interchatHook || - await newchannel.createWebhook({ - name: 'InterChat Network', - avatar: newchannel.client.user.avatarURL(), - }); - } - - else if (newchannel.isThread() && newchannel.parent) { - const webhooks = await newchannel.parent.fetchWebhooks(); - const interchatHook = webhooks?.find((hook) => hook.owner?.id === hook.client.user?.id); - - webhook = interchatHook || - await newchannel.parent.createWebhook({ - name: 'InterChat Network', - avatar: newchannel.client.user.avatarURL(), - }); - } - - await db.connectedList.update({ - where: { channelId: updConnection.channelId }, - data: { - channelId: newchannel.id, - parentId: newchannel?.isThread() ? newchannel.parentId : { unset: true }, - webhookURL: webhook?.url, - }, - }); - - await selected.update({ - content: `${emoji.normal.yes} Channel has been changed to ${newchannel}!`, - components: [], - }); - break; - } - - /* Invite Selection Response */ - case 'invite': { - await interaction.followUp({ - content: 'Setting an invite allows users to join your server through the `Server Info` context menu. Servers that go against our will be removed.', - ephemeral: true, - }); - - const modal = new ModalBuilder() - .setCustomId(settingsMenu.id) - .setTitle('Set Invite') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('invite_link') - .setStyle(TextInputStyle.Short) - .setLabel('Invite Link') - .setPlaceholder('Provide a invite link or code. Leave blank to remove.') - .setValue('https://discord.gg/') - .setMaxLength(35) - .setRequired(false), - ), - ); - - await settingsMenu.showModal(modal); - - const modalResp = await settingsMenu.awaitModalSubmit({ time: 60_000 }).catch((e) => { - if (!e.message.includes('reason: time')) { - logger.error(e); - captureException(e); - } - return null; - }); - - if (!modalResp) return; - - const link = modalResp.fields.getTextInputValue('invite_link'); - - if (!link) { - await db.connectedList.update({ - where: { channelId }, - data: { invite: { unset: true } }, - }); - modalResp.reply({ content: 'Invite unset.', ephemeral: true }); - return; - } - - const isValid = await modalResp.client?.fetchInvite(link).catch(() => null); - - if (!isValid || isValid.guild?.id !== modalResp.guild?.id) { - modalResp.reply({ - content: 'Invalid Invite.', - ephemeral: true, - }); - return; - } - - await db.connectedList.update({ where: { channelId: updConnection.channelId }, data: { invite: isValid.code } }); - - modalResp.reply({ - content: 'Invite link successfully set!', - ephemeral: true, - }); - break; - } - } - - settingsMenu.replied || settingsMenu.deferred - ? interaction.editReply({ embeds: [await setupEmbed(interaction, updConnection.channelId)] }) - : settingsMenu.update({ embeds: [await setupEmbed(interaction, updConnection.channelId)] }); - }); - - selectCollector.on('end', () => { - buttonCollector.stop('Components disabled.'); - interaction.editReply({ components: [] }).catch(() => null); - return; - }); - }, -}; diff --git a/src/Scripts/network/onboarding.ts b/src/Scripts/network/onboarding.ts deleted file mode 100644 index 2ef50962..00000000 --- a/src/Scripts/network/onboarding.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { ActionRowBuilder, ButtonStyle, ChatInputCommandInteraction, EmbedBuilder, ButtonBuilder, ComponentType, ButtonInteraction, AnySelectMenuInteraction, Collection } from 'discord.js'; -import { constants, rulesEmbed } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -const onboardingInProgress = new Collection(); - -/* Make user accept and understand important info on first setup */ -export default { - async execute( - interaction: ChatInputCommandInteraction | AnySelectMenuInteraction | ButtonInteraction, - hubName: string, - channelId: string, - ephemeral = false, - ) { - // Check if server is already attempting to join a hub - if (onboardingInProgress.has(channelId)) { - const err = { - content: `${emojis.normal.no} There has already been an attempt to join a hub in <#${channelId}>. Please wait for that to finish before trying again!`, - ephemeral, - }; - interaction.deferred || interaction.replied - ? interaction.followUp(err) - : interaction.reply(err); - return; - } - // Mark this as in-progress so server can't join twice - onboardingInProgress.set(channelId, channelId); - - const embed = new EmbedBuilder() - .setTitle(`👋 Hey there, welcome to ${hubName}!`) - .setDescription( - stripIndents` - To keep things organized, it's recommended to use a separate channel for just for this hub. But don't worry, you can always change this later. - - **How it works:** The InterChat Network is like a magic bridge that links channels on different servers that are with us in this hub. Learn more at our [guide](https://discord-interchat.github.io/docs). - `, - ) - .setColor(constants.colors.interchatBlue) - .setFooter({ text: `InterChat Network | Version ${interaction.client.version}` }); - - const nextButton = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger), - new ButtonBuilder().setCustomId('next').setLabel('Next').setStyle(ButtonStyle.Success), - ); - - const replyMsg = { - embeds: [embed], - components: [nextButton], - fetchReply: true, - ephemeral, - }; - - const reply = await (interaction.deferred - ? interaction.editReply(replyMsg) - : interaction.reply(replyMsg)); - - const filter = (i: ButtonInteraction) => i.user.id === interaction.user.id; - - const response = await reply - .awaitMessageComponent({ - time: 60_000 * 2, - filter, - componentType: ComponentType.Button, - }) - .catch(() => null); - - if (response?.customId === 'next') { - const acceptButton = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger), - new ButtonBuilder().setCustomId('accept').setLabel('Accept').setStyle(ButtonStyle.Success), - ); - - const acceptOnboarding = await response.update({ - embeds: [rulesEmbed], - components: [acceptButton], - }); - - const acceptResp = await acceptOnboarding - .awaitMessageComponent({ - time: 60_000, - filter, - componentType: ComponentType.Button, - }) - .catch(() => null); - - // To avoid getting interaction failures - await acceptResp?.deferUpdate(); - - // remove in-progress marker as onboarding has either been cancelled or completed - onboardingInProgress.delete(channelId); - - return acceptResp?.customId === 'accept' ? true : false; - } - - onboardingInProgress.delete(channelId); - return false; - }, -}; diff --git a/src/Scripts/networkLogs/modActions.ts b/src/Scripts/networkLogs/modActions.ts deleted file mode 100644 index ea6bf5b5..00000000 --- a/src/Scripts/networkLogs/modActions.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { constants, getDb } from '../../Utils/utils'; -import { EmbedBuilder, Guild, User } from 'discord.js'; -import { blacklistedServers } from '.prisma/client'; -import { captureMessage } from '@sentry/node'; -import emojis from '../../Utils/JSON/emoji.json'; - -interface actionUser { - user: User; - action: 'blacklistUser' | 'unblacklistUser'; - reason?: string | null; -} - -interface actionServer { - guild: { - id: string; - resolved?: Guild; - dbResult?: blacklistedServers; - }; - action: 'leave' | 'disconnect' | 'blacklistServer'; - reason?: string | null; -} - -interface blacklistServer extends actionServer { - action: 'blacklistServer'; - expires?: Date; -} - -interface blacklistUser extends actionUser { - action: 'blacklistUser'; - expires?: Date; -} -interface leaveServer extends actionServer { - action: 'leave'; -} -interface disconnectServer extends actionServer { - action: 'disconnect'; -} -interface unblacklistUser { - action: 'unblacklistUser'; - hubId: string; - user: User; - blacklistedFor?: string; -} - -interface unblacklistServer { - action: 'unblacklistServer'; - hubId: string; - oldBlacklist: blacklistedServers; - timestamp: Date; -} - -// TODO: Make the logs channel into a forum, which includes the folowing posts: -// Network Log - DONE -// Reports -// Judgement -export async function modActions(moderator: User, action: blacklistUser | unblacklistUser | blacklistServer | unblacklistServer | leaveServer | disconnectServer) { - const modLogs = await moderator.client.channels.fetch(constants.channel.modlogs); - const emoji = emojis.normal; - - if (!modLogs?.isTextBased()) return captureMessage('Modlogs channel is not text based. (modActions.ts)', 'warning'); - - let guild: Guild | undefined; - - if (action.action !== 'blacklistUser' && action.action !== 'unblacklistUser' && action.action !== 'unblacklistServer') { - guild = action.guild.resolved || moderator.client.guilds.cache.get(action.guild.id); - } - - switch (action.action) { - case 'blacklistUser': - await modLogs.send({ - embeds: [ - new EmbedBuilder() - .setAuthor({ name: moderator.username, iconURL: moderator.avatarURL()?.toString() }) - .setTitle('User Blacklisted') - .setDescription( - stripIndents` - ${emoji.dotBlue} **User:** ${action.user.username} (${action.user.id}) - ${emoji.dotBlue} **Moderator:** ${moderator.username} (${moderator.id}) - `, - ) - .addFields( - { name: 'Reason', value: `${action.reason || 'Not Provided.'}`, inline: true }, - { name: 'Blacklist Expires', value: action.expires ? `` : 'Never.', inline: true }, - ) - .setColor(constants.colors.interchatBlue), - ], - }); - break; - - - case 'blacklistServer': - await modLogs.send({ - embeds: [ - new EmbedBuilder() - .setAuthor({ name: String(guild?.name), iconURL: guild?.iconURL() || undefined }) - .setTitle('Server Blacklisted') - .setDescription(stripIndents` - ${emoji.dotBlue} **Server:** ${guild?.name} (${guild?.id}) - ${emoji.dotBlue} **Moderator:** ${moderator.username} (${moderator.id}) - `) - .addFields( - { name: 'Reason', value: `${action.reason || 'Not Provided.'}`, inline: true }, - { name: 'Blacklist Expires', value: action.expires ? `` : 'Never.', inline: true }, - ) - .setColor(constants.colors.interchatBlue), - ], - }); - break; - - case 'unblacklistUser': { - const hub = await getDb().hubs.findFirst({ where: { id: action.hubId } }); - await modLogs.send({ - embeds: [ - new EmbedBuilder() - .setAuthor({ name: moderator.username, iconURL: moderator.avatarURL()?.toString() }) - .setTitle('User Unblacklisted') - .setColor(constants.colors.interchatBlue) - .setDescription(stripIndents` - ${emoji.dotBlue} **User:** ${action.user.username} (${action.user.id}) - ${emoji.dotBlue} **Hub:** ${hub?.name} - ${emoji.dotBlue} **Moderator:** ${moderator.username} (${moderator.id}) - `) - .addFields( - { name: 'Blacklisted For', value: action.blacklistedFor || 'Unknown' }, - ), - ], - }); - break; - } - - case 'unblacklistServer': { - const server = await moderator.client.guilds.fetch(action.oldBlacklist.serverId).catch(() => null); - const serverName = server?.name || action.oldBlacklist.serverName; - const serverId = server?.id || action.oldBlacklist.serverId; - const blacklistData = action.oldBlacklist.hubs.find(({ hubId }) => hubId === action.hubId); - - await modLogs.send({ - embeds: [ - new EmbedBuilder() - .setAuthor({ name: `${serverName}`, iconURL: server?.iconURL()?.toString() }) - .setTitle('Server Unblacklisted') - .setDescription(stripIndents` - ${emoji.dotBlue} **Server:** ${serverName} (${serverId}) - ${emoji.dotBlue} **Moderator:** ${moderator.username} (${moderator.id}) - ${emoji.dotBlue} **Hub:** ${moderator.username} (${moderator.id}) - ${emoji.dotBlue} **Blacklisted for:** ${blacklistData?.reason} - `) - .setTimestamp(action.timestamp) - .setColor(constants.colors.interchatBlue), - ], - }); - break; - } - - case 'leave': - await modLogs.send({ - embeds: [ - new EmbedBuilder() - .setAuthor({ name: String(guild?.name), iconURL: guild?.iconURL() || undefined }) - .setTitle('Left Server') - .setDescription(stripIndents` - ${emoji.dotBlue} **Server:** ${guild?.name} (${guild?.id}) - ${emoji.dotBlue} **Moderator:** ${moderator.username} (${moderator.id}) - ${emoji.dotBlue} **Reason:** ${action.reason || 'Not Provided.'} - `) - .setColor(constants.colors.interchatBlue), - ], - }); - break; - - case 'disconnect': { - await modLogs.send({ - embeds: [ - new EmbedBuilder() - .setAuthor({ name: String(guild?.name), iconURL: guild?.iconURL() || undefined }) - .setTitle('Disconnected from Server') - .setDescription(stripIndents` - ${emoji.dotBlue} **Server:** ${guild?.name} (${guild?.id}) - ${emoji.dotBlue} **Moderator:** ${moderator.username} (${moderator.id}) - ${emoji.dotBlue} **Reason:** ${action.reason || 'Not Provided.'} - `) - .setColor(constants.colors.interchatBlue), - ], - }); - break; - } - - default: - break; - } -} diff --git a/src/Scripts/networkLogs/msgDelete.ts b/src/Scripts/networkLogs/msgDelete.ts deleted file mode 100644 index 8339abcf..00000000 --- a/src/Scripts/networkLogs/msgDelete.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { EmbedBuilder, GuildMember, GuildTextBasedChannel, Message } from 'discord.js'; -import { getDb, constants } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export async function networkMessageDelete(deletedBy: GuildMember | null, message: Message) { - const db = getDb(); - const messageInDb = await db?.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: { equals: message.id } } } }, - }); - - if (!messageInDb) return; - - const messageContent = message.content || message.embeds[0]?.description || 'No Content'; - const attachmentLink = message.attachments.first()?.url || message.embeds.at(0)?.image?.url || null; - - const author = await message.client.users.fetch(messageInDb.authorId).catch(() => null); - const deletedFrom = await message.client.guilds.fetch(messageInDb.serverId).catch(() => null); - - - const logChannel = await message.client.channels.fetch(constants.channel.networklogs) as GuildTextBasedChannel; - const embed = new EmbedBuilder() - .setAuthor({ name: String(author?.username), iconURL: author?.avatarURL()?.toString() }) - .setTitle('Message Deleted') - .setDescription(stripIndents` - ${messageContent} - - ${emojis.normal.dotRed} **Author:** ${author?.username} (${author?.id}) - ${emojis.normal.dotRed} **Deleted From:** ${deletedFrom?.name || 'Unknown'} (${messageInDb.serverId}) - ${emojis.normal.dotRed} **Attachments:** ${attachmentLink ? `[Click to view](${attachmentLink})` : 'None.'} - ${emojis.normal.dotRed} **Created At:** `) - .setFooter({ - text: `Deleted By: @${deletedBy?.user.username}`, - iconURL: deletedBy?.user.avatarURL() || deletedBy?.user.defaultAvatarURL, - }) - .setImage(attachmentLink) - .setTimestamp() - .setColor('Red'); - - logChannel.send({ embeds: [embed] }); -} diff --git a/src/Scripts/networkLogs/msgUpdate.ts b/src/Scripts/networkLogs/msgUpdate.ts deleted file mode 100644 index f6da217e..00000000 --- a/src/Scripts/networkLogs/msgUpdate.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { EmbedBuilder, GuildMember, GuildTextBasedChannel, Message } from 'discord.js'; -import { constants, getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export async function networkMsgUpdate(member: GuildMember, oldMessage: Message, newMessageContent: string) { - const db = getDb(); - const messageInDb = await db?.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: { equals: oldMessage.id } } } }, - }); - - const cbhqJumpMsg = messageInDb?.channelAndMessageIds.find((x) => x.channelId === '821607665687330816'); - const logChannel = await member.client.channels.fetch(constants.channel.networklogs) as GuildTextBasedChannel; - const attachmentLink = oldMessage.attachments.first()?.url || oldMessage.embeds.at(0)?.image?.url || null; - - const emoji = emojis; - - const embed = new EmbedBuilder() - .setAuthor({ name: member.user.username, iconURL: member.user.avatarURL()?.toString() }) - .setTitle('Message Edited') - .setDescription(stripIndents` - ${emoji.normal.dotYellow} **User:** ${member.user.username} (${member.id}) - ${emoji.normal.dotYellow} **Server:** ${member.guild.name} (${member.guild.id}) - ${emoji.normal.dotYellow} **Attachments:** ${attachmentLink ? `[Click to view](${attachmentLink})` : 'None.'} - ${emoji.normal.dotYellow} **Created At:** - [Jump To Message](https://discord.com/channels/${constants.guilds.cbhq}/${cbhqJumpMsg?.channelId}/${cbhqJumpMsg?.messageId})`) - .addFields( - { name: 'Before', value: oldMessage.content || oldMessage.embeds[0]?.description || 'None.' }, - { name: 'After', value: newMessageContent }, - ) - .setTimestamp() - .setImage(attachmentLink) - .setColor('Gold'); - - logChannel.send({ embeds: [embed] }); -} diff --git a/src/Scripts/reactions/reactionButton.ts b/src/Scripts/reactions/reactionButton.ts deleted file mode 100644 index 17f74edd..00000000 --- a/src/Scripts/reactions/reactionButton.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ButtonInteraction } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import updateMessageReactions from '../reactions/updateMessage'; -import { HubSettingsBitField } from '../../Utils/hubSettingsBitfield'; -import { fetchServerBlacklist, fetchUserBlacklist } from '../../Utils/blacklist'; - -export default { - async execute(interaction: ButtonInteraction) { - const db = getDb(); - const messageInDb = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: interaction.message.id } } }, - include: { hub: { select: { connections: { where: { connected: true } }, settings: true } } }, - }); - - if ( - !messageInDb?.hub || - !messageInDb.hubId || - !(new HubSettingsBitField(messageInDb.hub.settings).has('Reactions')) || - !interaction.inCachedGuild() - ) return interaction.reply({ content: 'This hub does not have reactions enabled.', ephemeral: true }); - - const userBlacklisted = await fetchUserBlacklist(messageInDb.hubId, interaction.user.id); - const serverBlacklisted = await fetchServerBlacklist(messageInDb.hubId, interaction.guild.id); - - if (userBlacklisted || serverBlacklisted) { - await interaction.reply({ - content: 'You are blacklisted from this hub.', - ephemeral: true, - }); - return; - } - - await interaction.deferUpdate(); - - const connections = await db.connectedList.findMany({ - where: { - channelId: { in: messageInDb?.channelAndMessageIds.map((c) => c.channelId) }, - connected: true, - }, - }); - - const reactedEmoji = interaction.customId.split('reaction_')[1]; - const dbReactions = messageInDb.reactions?.valueOf() as Record; - - if (dbReactions[reactedEmoji]) { - // If the user already reacted, remove the reaction - if (dbReactions[reactedEmoji].includes(interaction.user.id)) { - const userIndex = dbReactions[reactedEmoji].indexOf(interaction.user.id); - dbReactions[reactedEmoji].splice(userIndex, 1); - } - // Add the user to the array - else { - dbReactions[reactedEmoji].push(interaction.user.id); - } - } - - await db.messageData.update({ - where: { id: messageInDb.id }, - data: { reactions: dbReactions }, - }); - - // Update the message - updateMessageReactions.execute(connections, messageInDb.channelAndMessageIds, dbReactions); - }, -}; \ No newline at end of file diff --git a/src/Scripts/reactions/sortReactions.ts b/src/Scripts/reactions/sortReactions.ts deleted file mode 100644 index 859f5ef0..00000000 --- a/src/Scripts/reactions/sortReactions.ts +++ /dev/null @@ -1,9 +0,0 @@ -export default { - execute(reactions: Record) { - // Sort the array based on the reaction counts - /* { '👍': ['10201930193'], '👎': ['10201930193'] } // before Object.entries - => [ [ '👎', ['10201930193'] ], [ '👍', ['10201930193'] ] ] // after Object.entries - */ - return Object.entries(reactions).sort((a, b) => b[1].length - a[1].length); - }, -}; \ No newline at end of file diff --git a/src/Scripts/reactions/updateMessage.ts b/src/Scripts/reactions/updateMessage.ts deleted file mode 100644 index 11c1aac8..00000000 --- a/src/Scripts/reactions/updateMessage.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - WebhookClient, - ComponentType, -} from 'discord.js'; -import { MessageDataChannelAndMessageIds, connectedList } from '@prisma/client'; -import sortReactions from './sortReactions'; - -export default { - execute( - connections: connectedList[], - channelAndMessageIds: MessageDataChannelAndMessageIds[], - reactions: Record, - ) { - // reactions will contain something like this: { '👍': ['userId1', 'userId2'], '👎': ['userId1', 'userId2', 'userId3'] } - // sortedReactions[0] = array of [emoji, users[]] - // sortedReactions[0][0] = emoji - // sortedReactions[0][1] = array of users - const sortedReactions = sortReactions.execute(reactions); - const reactionCount = sortedReactions[0][1].length; - const mostReaction = sortedReactions[0][0]; - - const reactionBtn = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`reaction_${mostReaction}`) - .setEmoji(mostReaction) - .setStyle(ButtonStyle.Secondary) - .setLabel(`${reactionCount}`), - ); - - if (sortedReactions.length > 1) { - const allReactionCount = sortedReactions.filter((e) => e[0] !== mostReaction && e[1].length > 0); - if (allReactionCount.length > 0) { - reactionBtn.addComponents( - new ButtonBuilder() - .setCustomId('view_all_reactions') - .setStyle(ButtonStyle.Secondary) - .setLabel(`+ ${allReactionCount.length}`), - ); - } - } - - connections.forEach(async (connection) => { - const dbMsg = channelAndMessageIds.find((e) => e.channelId === connection.channelId); - if (!dbMsg) return; - - const webhook = new WebhookClient({ url: connection.webhookURL }); - const message = await webhook - .fetchMessage(dbMsg.messageId, { - threadId: connection.parentId ? connection.channelId : undefined, - }) - .catch(() => null); - - const components = message?.components?.filter((row) => { - // filter all buttons that are not reaction buttons - row.components = row.components.filter((component) => { - return component.type === ComponentType.Button && - component.style === ButtonStyle.Secondary - ? !component.custom_id.startsWith('reaction_') && - component.custom_id !== 'view_all_reactions' - : true; - }); - - // if the filtered row has components, that means it has components other than reaction buttons - // so we return true to keep the row - return row.components.length > 0; - }); - - if (reactionCount > 0) components?.push(reactionBtn.toJSON()); - - webhook - .editMessage(dbMsg.messageId, { - components, - threadId: connection.parentId ? connection.channelId : undefined, - }) - .catch(() => null); - }); - }, -}; diff --git a/src/Scripts/reactions/viewReactionsMenu.ts b/src/Scripts/reactions/viewReactionsMenu.ts deleted file mode 100644 index a5d3c5e5..00000000 --- a/src/Scripts/reactions/viewReactionsMenu.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { ActionRowBuilder, ButtonInteraction, ComponentType, EmbedBuilder, StringSelectMenuBuilder } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import sortReactions from './sortReactions'; -import updateMessageReactions from './updateMessage'; -import { HubSettingsBitField } from '../../Utils/hubSettingsBitfield'; -import emojis from '../../Utils/JSON/emoji.json'; -import { fetchServerBlacklist, fetchUserBlacklist } from '../../Utils/blacklist'; - -export default { - async execute(interaction: ButtonInteraction) { - const db = getDb(); - const target = interaction.message; - const networkMessage = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: target.id } } }, - include: { hub: { select: { connections: { where: { connected: true } }, settings: true } } }, - }); - - if ( - !networkMessage?.reactions || - !networkMessage.hubId - ) { - await interaction.reply({ - content: 'There are no more reactions to view.', - ephemeral: true, - }); - return; - } - - const userBlacklisted = await fetchUserBlacklist(networkMessage.hubId, interaction.user.id); - const serverBlacklisted = await fetchServerBlacklist(networkMessage.hubId, interaction.guildId || ''); - - if (userBlacklisted || serverBlacklisted) { - await interaction.reply({ - content: 'You are blacklisted from this hub.', - ephemeral: true, - }); - return; - } - - const reactions = networkMessage.reactions?.valueOf() as Record; - const sortedReactions = sortReactions.execute(reactions); - let totalReactions = 0; - let reactionString = ''; - const reactionMenu = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('add_reaction') - .setPlaceholder('Add a reaction'), - ); - - const hubSettings = new HubSettingsBitField(networkMessage.hub?.settings); - if (!hubSettings.has('Reactions')) reactionMenu.components[0].setDisabled(true); - - - sortedReactions.forEach((r, index) => { - if (r[1].length === 0 || index >= 10) return; - reactionMenu.components[0].addOptions({ - label: 'React/Unreact', - value: r[0], - emoji: r[0], - }); - totalReactions++; - reactionString += `- ${r[0]}: ${r[1].length}\n`; - }); - - - const embed = new EmbedBuilder() - .setThumbnail(interaction.client.user.displayAvatarURL()) - .setDescription(stripIndents` - ## ${emojis.normal.clipart} Reactions - - ${reactionString || 'No reactions yet!'} - - **Total Reactions:** - __${totalReactions}__ - `) - .setColor('Random'); - - const resp = await interaction.reply({ - embeds: [embed], - components: [reactionMenu], - ephemeral: true, - fetchReply: true, - }); - - const collector = resp.createMessageComponentCollector({ - idle: 60_000, - filter: (i) => i.user.id === interaction.user.id && i.customId === 'add_reaction', - componentType: ComponentType.StringSelect, - }); - - collector.on('collect', async (i) => { - const messageInDb = await db.messageData.findFirst({ where: { id: networkMessage.id } }); - if (!messageInDb || !messageInDb.reactions) return; - - const dbReactions = messageInDb.reactions.valueOf() as Record; - const reactedEmoji = i.values[0]; - if (!dbReactions[reactedEmoji]) return; - - // If the user already reacted, remove the reaction - if (dbReactions[reactedEmoji].includes(interaction.user.id)) { - const userIndex = dbReactions[reactedEmoji].indexOf(interaction.user.id); - dbReactions[reactedEmoji].splice(userIndex, 1); - await i.reply({ - content: `You have unreacted with ${reactedEmoji}`, - ephemeral: true, - }); - } - // Add the user to the array - else { - dbReactions[reactedEmoji].push(interaction.user.id); - await i.reply({ - content: `You have reacted with ${reactedEmoji}`, - ephemeral: true, - }); - } - - await db.messageData.update({ - where: { id: messageInDb.id }, - data: { reactions: dbReactions }, - }); - - if (networkMessage.hub) { - updateMessageReactions.execute(networkMessage.hub.connections, messageInDb.channelAndMessageIds, dbReactions); - } - }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/server/leave.ts b/src/Scripts/server/leave.ts deleted file mode 100644 index f858ee35..00000000 --- a/src/Scripts/server/leave.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { sendInFirst } from '../../Utils/utils'; -import logger from '../../Utils/logger'; -import { modActions } from '../networkLogs/modActions'; - -export default { - execute: async (interaction: ChatInputCommandInteraction) => { - const serverOpt = interaction.options.getString('server', true); - const reason = interaction.options.getString('reason', true); - const notify = interaction.options.getBoolean('notify') ?? true; // if not set, default to true - let server; - - try { - server = await interaction.client.guilds.fetch(serverOpt); - } - catch (err) { - await interaction.reply('I am not in that server.'); - return; - } - - if (notify) { - await sendInFirst(server, - `I am leaving this server due to reason **${reason}**. Please contact the staff from the support server if you think that the reason is not valid.`, - ); - } - await server.leave(); - await interaction.reply(`I have left the server ${server.name} due to reason "${reason}".`); - - modActions(interaction.user, { - guild: { - id: server.id, - resolved: server, - }, - action: 'leave', - reason, - }); - logger.info(`Left server ${server.name} due to reason \`${reason}\``); - }, -}; diff --git a/src/Scripts/suggestions/takedown.ts b/src/Scripts/suggestions/takedown.ts deleted file mode 100644 index ced9f4d6..00000000 --- a/src/Scripts/suggestions/takedown.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder, ForumChannel } from 'discord.js'; -import { constants } from '../../Utils/utils'; - -export default { - execute: async (interaction: ChatInputCommandInteraction) => { - const keep = interaction.options.getBoolean('keepmessage'); - const postId = interaction.options.getString('postid'); - const suggestionChannel = await interaction.client.channels.fetch(constants.channel.suggestions) as ForumChannel | null; - - let suggestionPost; - let suggestionMessage; - - try { - suggestionPost = await suggestionChannel?.threads.fetch(String(postId)); - } - catch { - return interaction.reply('Unable to locate the forum post.'); - } - - try { - suggestionMessage = await suggestionPost?.fetchStarterMessage(); - } - catch { - return interaction.reply('Unable to locate the posted message.'); - } - - if (suggestionMessage?.author.id != interaction.client.user?.id) { - await interaction.reply('Unable to locate the message. Please make sure the message ID is valid.'); - return; - } - - if (keep) { - suggestionMessage?.edit({ - embeds: [ - new EmbedBuilder() - .setDescription('*This suggestion was taken down.*') - .setColor(constants.colors.invisible), - ], - }); - } - else { - suggestionPost?.setLocked(true); - try { - await suggestionMessage?.delete(); - } - catch { - interaction.channel?.send('Unable to delete message!'); - } - } - - await interaction.reply('🗑️ Suggestion discarded.'); - suggestionPost?.setArchived(true, `Suggestion taken down by ${interaction.user.username}`); - }, -}; diff --git a/src/Scripts/suggestions/update.ts b/src/Scripts/suggestions/update.ts deleted file mode 100644 index cd9fc848..00000000 --- a/src/Scripts/suggestions/update.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder, ForumChannel } from 'discord.js'; -import { constants } from '../../Utils/utils'; - -type suggestionStatus ='✅ Approved' | '🧑‍💻 Pending' | '✅ Implemented' | '❌ Rejected' | '🚫 Closed'; - -export default { - execute: async (interaction: ChatInputCommandInteraction) => { - const postId = interaction.options.getString('postid'); - const status = interaction.options.getString('status') as suggestionStatus | null; - const reason = interaction.options.getString('reason'); - const suggestionChannel = await interaction.client.channels.fetch(constants.channel.suggestions) as ForumChannel | null; - const suggestionPost = await suggestionChannel?.threads?.fetch(String(postId)).catch(() => { interaction.reply('Unable to locate the message. Please make sure the message ID is valid.');}); - const suggestionMessage = await suggestionPost?.fetchStarterMessage(); - - if (!suggestionMessage - || suggestionMessage.embeds.length < 1 - || suggestionMessage.author.id != interaction.client.user?.id - || suggestionMessage.embeds[0].description?.includes('taken down') - ) { - await interaction.reply('Unable to locate the message. Please make sure the message ID is valid.'); - return; - } - const suggestionEmbed = new EmbedBuilder(suggestionMessage?.embeds[0].toJSON()); - suggestionEmbed.setFields({ name: 'Status', value: String(status) }); - - if (reason) suggestionEmbed.addFields({ name: 'Message From Staff/Developers', value: String(reason) }); - - await suggestionMessage?.edit({ embeds: [suggestionEmbed] }); - - if (status === '🚫 Closed' || status === '❌ Rejected') suggestionPost?.setArchived(true, `Closed by ${interaction.user.username}`); - - interaction.reply({ content: 'Updated suggestion!', ephemeral: true }).catch(); - - }, -}; \ No newline at end of file diff --git a/src/Scripts/support/report.ts b/src/Scripts/support/report.ts deleted file mode 100644 index 13f9f34d..00000000 --- a/src/Scripts/support/report.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { captureMessage } from '@sentry/node'; -import { stripIndents } from 'common-tags'; -import { ActionRowBuilder, EmbedBuilder, TextInputBuilder, ModalBuilder, TextInputStyle, ChatInputCommandInteraction, ForumChannel, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ComponentType, ThreadChannel } from 'discord.js'; -import { constants } from '../../Utils/utils'; -import logger from '../../Utils/logger'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const reportType = interaction.options.getString('type', true) as 'user' | 'server' | 'bug' | 'other'; - const reportModal = new ModalBuilder() - .setTitle('New Report') - .setCustomId(interaction.id) - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('description') - .setLabel('Report Details') - .setPlaceholder('A detailed description of the report.') - .setStyle(TextInputStyle.Paragraph) - .setMinLength(10) - .setMaxLength(950), - ), - ); - - const { normal: normalEmojis } = emojis; - - if (reportType === 'bug') { - const bugSelect = new ActionRowBuilder() - .addComponents( - new StringSelectMenuBuilder() - .setMaxValues(2) - .setCustomId('component') - .setOptions( - new StringSelectMenuOptionBuilder() - .setLabel('Commands') - .setEmoji(normalEmojis.slash) - .setValue('Commands'), - new StringSelectMenuOptionBuilder() - .setLabel('Network') - .setEmoji(normalEmojis.clipart) - .setValue('Network'), - new StringSelectMenuOptionBuilder() - .setLabel('Other (Specify)') - .setEmoji('❓') - .setValue('Other'), - ), - ); - - const bugEmbed = new EmbedBuilder() - .setTitle('Affected Components') - .setDescription('Please choose what component of the bot you are facing issues with.') - .setColor('Random'); - - const message = await interaction.reply({ - embeds: [bugEmbed], - components: [bugSelect], - ephemeral: true, - }); - - // get selected bug type - const typeSelection = await message.awaitMessageComponent({ - componentType: ComponentType.StringSelect, - time: 30_000, - }).catch(() => null); - - reportModal.setComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('summary') - .setLabel('Whats the bug about?') - .setPlaceholder('Frequent interaction failures...') - .setStyle(TextInputStyle.Short)), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('description') - .setLabel('Detailed Description (OPTIONAL)') - .setPlaceholder('Please describe the steps to reproduce the issue, include any unexpected behavior.') - .setStyle(TextInputStyle.Paragraph) - .setRequired(false) - .setMinLength(17), - )); - - // show modal to collect extra information - await typeSelection?.showModal(reportModal); - - // get modal input and post it to support server forum - typeSelection?.awaitModalSubmit({ - time: 60_000 * 5, - filter: (i) => i.customId === reportModal.data.custom_id, - }).then(async (bugModal) => { - const bugReportChannel = await bugModal.client.channels.fetch(constants.channel.bugs).catch(() => null) as ForumChannel | null; - if (!bugReportChannel) { - logger.error('Bug report channel not found.'); - captureMessage('Bug report channel not found.', { user: { id: bugModal.user.id, username: bugModal.user.username }, extra: { command: 'Bug Report' } }); - return bugModal.reply({ - content: 'An error occured while sending your report. The developers have been notified!', - ephemeral: true, - }); - } - - const summary = bugModal.fields.getTextInputValue('summary'); - const description = bugModal.fields.getTextInputValue('description'); - - const bugReportEmbed = new EmbedBuilder() - .setColor(constants.colors.invisible) - .setTitle(summary) - .setDescription(`**Affects:** ${typeSelection.values.join(', ')}`) - .setThumbnail(interaction.user.avatarURL({ size: 2048 }) ?? interaction.user.defaultAvatarURL) - .setFooter({ - text: `Reported by ${interaction.user.username} (${interaction.user.id})`, - iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, - }); - - if (description) bugReportEmbed.addFields({ name: 'Details', value: description }); - - const appliedTags = typeSelection.values - .map((value) => bugReportChannel.availableTags.find((tag) => tag.name.includes(value))?.id || 'error lmao'); - - // finally make the post in ic central - await bugReportChannel.threads.create({ - name: summary, - message: { embeds: [bugReportEmbed] }, - appliedTags, - }); - - bugModal.reply({ - content: `${normalEmojis.yes} Successfully submitted report. Join the to view and/or attach screenshots to it.`, - ephemeral: true, - }); - }).catch((error) => { - if (!error.message.includes('ending with reason: time')) logger.error(error); - }); - return; - } - - else if (reportType === 'server' || reportType === 'user') { - reportModal.addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('id') - .setLabel('User/Server ID') - .setPlaceholder('The IDs of the user/server you are reporting.') - .setStyle(TextInputStyle.Short) - .setMinLength(17) - .setMaxLength(20), - )); - } - - await interaction.showModal(reportModal); - interaction.awaitModalSubmit({ time: 60000 * 5, filter: (i) => i.user.id === interaction.user.id && i.customId === reportModal.data.custom_id }) - .then(async modalInteraction => { - const reportChannel = await modalInteraction.client.channels.fetch(constants.channel.reports).catch(() => null) as ThreadChannel | null; - const reportDescription = modalInteraction.fields.getTextInputValue('description'); - - switch (reportType) { - case 'user': { - const Ids = modalInteraction.fields.getTextInputValue('id'); - const reportedUser = await interaction.client.users.fetch(Ids).catch(() => null); - if (!reportedUser) { - return modalInteraction.reply({ - content: stripIndents` - ${normalEmojis.no} I couldn't find a user with that ID.\n\n - **To find a user's ID within the network, please follow these instructions:** - ${normalEmojis.dotYellow} Right click on a message sent from the user in question select \`Apps > User Info\`. Please double-check the ID and try again. - `, - ephemeral: true, - }); - } - - const userReport = new EmbedBuilder() - .setColor('Red') - .setTitle('New User Report') - .setDescription(`Username: ${reportedUser.username}\nUser Id: ${reportedUser.id}`) - .setFields({ name: 'Reason for report', value: reportDescription }) - .setThumbnail(reportedUser.avatarURL({ size: 2048 }) ?? reportedUser.defaultAvatarURL) - .setFooter({ text: `Reported by ${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL }); - await reportChannel?.send({ content: '<@&1088677008260726854>', embeds: [userReport] }); - break; - } - - case 'server': { - const ids = modalInteraction.fields.getTextInputValue('id'); - const reportedServer = await interaction.client.guilds.fetch(ids).catch(() => null); - if (!reportedServer) { - return modalInteraction.reply({ - content: stripIndents` - ${normalEmojis.no} I couldn't find a server with that ID.\n - **To find a server ID within the network, please follow these instructions:** - ${normalEmojis.dotYellow} Right click on a message sent by the server in question and select \`Apps > Server Info\`. Please double-check the ID and try again. - `, - ephemeral: true, - }); - } - - const serverReport = new EmbedBuilder() - .setColor('Red') - .setTitle('New Server Report') - .setDescription(`Server Name: ${reportedServer.name}\nServer Id: ${reportedServer.id}`) - .setFields({ name: 'Reason for report', value: reportDescription }) - .setThumbnail(reportedServer.iconURL({ size: 2048 })) - .setFooter({ text: `Reported by ${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL }); - await reportChannel?.send({ content: '<@&1088677008260726854>', embeds: [serverReport] }); - break; - } - default: { - const otherReport = new EmbedBuilder() - .setColor('Random') - .setTitle('New Report') - .setDescription('**Type:** Other') - .setFields({ name: 'Description', value: reportDescription }) - .setFooter({ text: `Reported by ${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL }); - await reportChannel?.send({ content: '<@&1088677008260726854>', embeds: [otherReport] }); - break; - } - } - await modalInteraction.reply({ content: 'Report submitted. Join the support server to get updates on your report.', ephemeral: true }); - }).catch((error) => {if (!error.message.includes('ending with reason: time')) logger.error(error);}); - - }, -}; diff --git a/src/Scripts/support/server.ts b/src/Scripts/support/server.ts deleted file mode 100644 index f346853b..00000000 --- a/src/Scripts/support/server.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { constants } from '../../Utils/utils'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const embed = new EmbedBuilder() - .setTitle('InterChat Central') - .setDescription('[Click Here]()') - .setColor(constants.colors.interchatBlue) - .setTimestamp(); - await interaction.reply({ embeds: [embed] }); - }, -}; \ No newline at end of file diff --git a/src/Scripts/support/suggest.ts b/src/Scripts/support/suggest.ts deleted file mode 100644 index 65872931..00000000 --- a/src/Scripts/support/suggest.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { EmbedBuilder, ActionRowBuilder, ChatInputCommandInteraction, TextInputBuilder, ModalBuilder, TextInputStyle, ChannelType } from 'discord.js'; -import { constants } from '../../Utils/utils'; -import logger from '../../Utils/logger'; - -export default { - async execute(interaction: ChatInputCommandInteraction) { - const suggestionChannel = await interaction.client.channels.fetch(constants.channel.suggestions); - - if (suggestionChannel?.type !== ChannelType.GuildForum) return interaction.reply('An error occured when trying to send your suggestion! Please join the server to manually suggest in the suggestion channel or report a bug.'); - const suggestionTag = suggestionChannel.availableTags.find(tag => tag.name === 'Bot Related'); - - - // modal - const modal = new ModalBuilder() - .setTitle('Suggestion') - .setCustomId(interaction.id) - .addComponents( - new ActionRowBuilder() - .addComponents( - new TextInputBuilder() - .setCustomId('Title') - .setLabel('Title') - .setStyle(TextInputStyle.Short) - .setRequired(true), - ), - - new ActionRowBuilder() - .addComponents( - new TextInputBuilder() - .setCustomId('Description') - .setLabel('What is your suggestion about?') - .setStyle(TextInputStyle.Paragraph) - .setRequired(true) - .setMaxLength(950), - ), - ); - - - await interaction.showModal(modal); - - interaction.awaitModalSubmit({ - filter: (m) => m.user.id === interaction.user.id, - time: 60000, - }).then(async (modalInteraction) => { - const attachment = interaction.options.getAttachment('screenshot'); - const title = modalInteraction.fields.getTextInputValue('Title'); - const description = modalInteraction.fields.getTextInputValue('Description'); - - const suggestionEmbed = new EmbedBuilder() - .setAuthor({ name: `Suggested by @${modalInteraction.user.username}`, iconURL: modalInteraction.user.displayAvatarURL() }) - .setDescription(description) - .setImage(attachment?.url as string | null) - .setColor(constants.colors.interchatBlue) - .addFields({ - name: 'Status', - value: '🧑‍💻 Pending', - }); - - - try { - await suggestionChannel?.threads.create({ - name: title, - appliedTags: suggestionTag ? [suggestionTag.id] : undefined, - message: { - content: 'This suggestion was sent using `/support suggest`.', - embeds: [suggestionEmbed], - }, - }); - } - catch (err) { - logger.error('Error while creating suggestion post:', err); - return modalInteraction.reply({ content: 'An error occured while making your suggestion! The developers have been alerted.', ephemeral: true }); - } - modalInteraction.reply({ content: 'Suggestion sent! Join the support server to see it.', ephemeral: true }); - }).catch(() => {return;}); - - }, -}; \ No newline at end of file diff --git a/src/Scripts/warn/add.ts b/src/Scripts/warn/add.ts deleted file mode 100644 index 75f581e7..00000000 --- a/src/Scripts/warn/add.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { constants, getDb } from '../../Utils/utils'; -import { randomUUID } from 'crypto'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - execute: async (interaction: ChatInputCommandInteraction) => { - await interaction.deferReply(); - - const db = getDb(); - const user = interaction.options.getUser('user', true); - const userWarns = await db.userWarns.findFirst({ where: { userId: user.id } }); - - const warning = { - id: randomUUID(), - reason: interaction.options.getString('reason', true), - moderatorId: interaction.user.id, - timestamp: new Date(), - automated: false, - }; - - if (!userWarns) { - await db.userWarns.create({ - data: { userId: user.id, userTag: user.tag, warnings: [warning] }, - }); - } - else { - await db.userWarns.update({ - where: { userId: userWarns.userId }, - data: { - userId: user.id, - userTag: user.tag, - warnings: [...userWarns.warnings, warning], - }, - }); - } - - const notifyEmbed = new EmbedBuilder() - .setTitle('🔨 You have been warned!') - .setDescription('You have issued warn in the network.') - .addFields([ - { name: 'Warning', value: `#${userWarns ? userWarns.warnings.length + 1 : 1}`, inline: true }, - { name: 'Reason', value: warning.reason, inline: true }, - ]) - .setFooter({ text: 'Join the support server if you you think the reason is not valid.', iconURL: interaction.client.user.avatarURL() || undefined }) - .setTimestamp() - .setColor(constants.colors.invisible); - - const notified = await user.send({ embeds: [notifyEmbed] }).catch(() => null); - await interaction.editReply(` ${emojis.normal.yes} Warned ${user.tag}! ${notified ? 'Notified them about their warn.' : 'I couldn\'t DM them.'}`); - }, -}; diff --git a/src/Scripts/warn/clear.ts b/src/Scripts/warn/clear.ts deleted file mode 100644 index d8e8a1af..00000000 --- a/src/Scripts/warn/clear.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - execute: async (interaction: ChatInputCommandInteraction) => { - const db = getDb(); - const user = interaction.options.getUser('user', true); - const userWarns = await db.userWarns.findFirst({ where: { userId: user.id } }); - - if (!userWarns?.warnings) { - return interaction.reply({ - content: `${emojis.normal.no} There are no warnings to remove!`, - ephemeral: true, - }); - } - - await db.userWarns.delete({ where: { userId: user.id } }); - await interaction.reply(`${emojis.normal.yes} Successfully cleard all warnings from @${user.username}!`); - }, -}; diff --git a/src/Scripts/warn/remove.ts b/src/Scripts/warn/remove.ts deleted file mode 100644 index 92ab857f..00000000 --- a/src/Scripts/warn/remove.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { getDb } from '../../Utils/utils'; -import emojis from '../../Utils/JSON/emoji.json'; - -export default { - execute: async (interaction: ChatInputCommandInteraction) => { - const db = getDb(); - const warnId = interaction.options.getString('id', true); - const user = interaction.options.getUser('user', true); - const userWarns = await db.userWarns.findFirst({ where: { userId: user.id } }); - - if (!userWarns?.warnings.some((warn) => warn.id === warnId)) { - return interaction.reply({ - content: `${emojis.normal.no} There are no warnings to remove!`, - ephemeral: true, - }); - } - - await db.userWarns.update({ - where: { userId: user.id }, - data: { - userTag: user.username, - warnings: { deleteMany: { where: { id: { equals: warnId } } } }, - }, - }); - - await interaction.reply(`${emojis.normal.yes} Successfully removed warning **${warnId}** from ${user.username}!`); - }, -}; diff --git a/src/SuperClient.ts b/src/SuperClient.ts new file mode 100644 index 00000000..8344710d --- /dev/null +++ b/src/SuperClient.ts @@ -0,0 +1,99 @@ +import { + Client, + IntentsBitField, + Partials, + Options, + Collection, + Guild, + Snowflake, +} from 'discord.js'; +import { ClusterClient, getInfo } from 'discord-hybrid-sharding'; +import { BlacklistManager } from './structures/BlacklistManager.js'; +import { commandsMap, interactionsMap } from './commands/Command.js'; +import { Scheduler } from './structures/Scheduler.js'; +import Logger from './utils/Logger.js'; +import CommandHandler from './structures/CommandHandler.js'; +import NetworkManager from './structures/NetworkManager.js'; +import ReactionUpdater from './updater/ReactionUpdater.js'; +import NSFWClient from './structures/NSFWDetection.js'; + +export default abstract class SuperClient extends Client { + readonly logger = Logger; + + readonly description = 'The only cross-server communication bot you\'ll ever need.'; + readonly version = process.env.npm_package_version ?? 'Unknown'; + readonly commands = commandsMap; + readonly components = interactionsMap; + + readonly commandCooldowns = new Collection(); + readonly reactionCooldowns = new Collection(); + readonly cluster = new ClusterClient(this); + + private readonly scheduler = new Scheduler(); + private readonly commandHandler = new CommandHandler(this); + private readonly networkHandler = new NetworkManager(this); + private readonly blacklistManager = new BlacklistManager(this.scheduler); + private readonly reactionUpdater = new ReactionUpdater(this); + private readonly nsfwDetector = new NSFWClient(); + + private static self: SuperClient; + + constructor() { + super({ + shards: getInfo().SHARD_LIST, // An array of shards that will get spawned + shardCount: getInfo().TOTAL_SHARDS, // Total number of shards + makeCache: Options.cacheWithLimits({ + MessageManager: 200, + PresenceManager: 0, + ReactionManager: 200, + }), + partials: [Partials.Message], + intents: [ + IntentsBitField.Flags.MessageContent, + IntentsBitField.Flags.Guilds, + IntentsBitField.Flags.GuildMembers, + IntentsBitField.Flags.GuildMessages, + IntentsBitField.Flags.GuildMessageReactions, + ], + }); + } + + protected init() { + SuperClient.self = this; + } + + public static getInstance(): SuperClient { + return this.self; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private resolveEval = (value: any[]): T | undefined => value?.find((res) => !!res); + + async fetchGuild(guildId: Snowflake): Promise { + const fetch = await this.shard?.broadcastEval( + (client, guildID) => client.guilds.cache.get(guildID), + { context: guildId }, + ); + + return fetch ? this.resolveEval(fetch) : undefined; + } + + getCommandManager(): CommandHandler { + return this.commandHandler; + } + getNetworkManager(): NetworkManager { + return this.networkHandler; + } + getScheduler(): Scheduler { + return this.scheduler; + } + getBlacklistManager(): BlacklistManager { + return this.blacklistManager; + } + getReactionUpdater(): ReactionUpdater { + return this.reactionUpdater; + } + getNSFWDetector(): NSFWClient { + return this.nsfwDetector; + } +} diff --git a/src/Utils/JSON/README.md b/src/Utils/JSON/README.md deleted file mode 100644 index 1fb661f2..00000000 --- a/src/Utils/JSON/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Open badwords.json at your own risk. It contains severe slurs and swear words. - -> **Note** -> Badge names in `emoji.json` must be the same as the names used in the userBadges database. diff --git a/src/Utils/JSON/badwords.json b/src/Utils/JSON/badwords.json deleted file mode 100644 index a3bfe627..00000000 --- a/src/Utils/JSON/badwords.json +++ /dev/null @@ -1,391 +0,0 @@ -{ - "slurs": [ - "nigga", - "nigg", - "nigger", - "n1gger", - "n1gg3r", - "niger", - "kys" - ], - - "badwords": [ - "4r5e", - "5h1t", - "5hit", - "a55", - "anal", - "ar5e", - "arrse", - "arse", - "ass", - "ass-fucker", - "asses", - "assfucker", - "assfukka", - "asshole", - "assholes", - "asswhole", - "a_s_s", - "b!tch", - "b00bs", - "b17ch", - "b1tch", - "ballbag", - "ballsack", - "beastial", - "beastiality", - "bellend", - "bestial", - "bestiality", - "bi+ch", - "biatch", - "bitch", - "bitcher", - "bitchers", - "bitches", - "bitchin", - "bitching", - "bloody", - "blow job", - "blowjob", - "blowjobs", - "boiolas", - "bollock", - "bollok", - "boner", - "boob", - "boobs", - "booobs", - "boooobs", - "booooobs", - "booooooobs", - "buceta", - "bugger", - "bunny fucker", - "c0ck", - "c0cksucker", - "carpet muncher", - "cawk", - "chink", - "cipa", - "cl1t", - "clit", - "clitoris", - "clits", - "cnut", - "cock", - "cock-sucker", - "cockface", - "cockhead", - "cockmunch", - "cockmuncher", - "cocks", - "cocksuck", - "cocksucked", - "cocksucker", - "cocksucking", - "cocksucks", - "cocksuka", - "cocksukka", - "cok", - "cokmuncher", - "coksucka", - "coon", - "cox", - "cum", - "cummer", - "cumming", - "cums", - "cumshot", - "cunilingus", - "cunillingus", - "cunnilingus", - "cunt", - "cuntlick", - "cuntlicker", - "cuntlicking", - "cunts", - "cyalis", - "cyberfuc", - "cyberfuck", - "cyberfucked", - "cyberfucker", - "cyberfuckers", - "cyberfucking", - "d1ck", - "dick", - "dickhead", - "dildo", - "dildos", - "dirsa", - "dlck", - "dog-fucker", - "donkeyribber", - "doosh", - "duche", - "dyke", - "ejaculate", - "ejaculated", - "ejaculates", - "ejaculating", - "ejaculatings", - "ejaculation", - "ejakulate", - "f u c k", - "f u c k e r", - "f4nny", - "fag", - "fagging", - "faggitt", - "faggot", - "faggs", - "fagot", - "fagots", - "fags", - "fanny", - "fannyflaps", - "fannyfucker", - "fanyy", - "fcuk", - "fcuker", - "fcuking", - "feck", - "fecker", - "felching", - "fellate", - "fellatio", - "fingerfuck", - "fingerfucked", - "fingerfucker", - "fingerfuckers", - "fingerfucking", - "fingerfucks", - "fistfuck", - "fistfucked", - "fistfucker", - "fistfuckers", - "fistfucking", - "fistfuckings", - "fistfucks", - "flange", - "fook", - "fooker", - "fuck", - "fucka", - "fucked", - "fucker", - "fuckers", - "fuckhead", - "fuckheads", - "fuckin", - "fucking", - "fuckings", - "fuckingshitmotherfucker", - "fuckme", - "fucks", - "fuckwhit", - "fuckwit", - "fudge packer", - "fudgepacker", - "fuk", - "fuker", - "fukker", - "fukkin", - "fuks", - "fukwhit", - "fukwit", - "fux", - "fux0r", - "f_u_c_k", - "gangbang", - "gangbanged", - "gangbangs", - "gaylord", - "gaysex", - "goatse", - "hardcoresex", - "heshe", - "hoar", - "hoare", - "hoer", - "homo", - "hore", - "hotsex", - "jism", - "jiz", - "jizm", - "jizz", - "kawk", - "knob", - "knobead", - "knobed", - "knobend", - "knobhead", - "knobjocky", - "knobjokey", - "kock", - "kondum", - "kondums", - "kunilingus", - "l3i+ch", - "l3itch", - "labia", - "m0f0", - "m0fo", - "m45terbate", - "ma5terb8", - "ma5terbate", - "masochist", - "master-bate", - "masterb8", - "masterbat*", - "masterbat3", - "masterbate", - "masterbation", - "masterbations", - "masturbate", - "mothafuck", - "mothafucka", - "mothafuckas", - "mothafuckaz", - "mothafucked", - "mothafucker", - "mothafuckers", - "mothafuckin", - "mothafucking", - "mothafuckings", - "mothafucks", - "mother fucker", - "motherfuck", - "motherfucked", - "motherfucker", - "motherfuckers", - "motherfuckin", - "motherfucking", - "motherfuckings", - "motherfuckka", - "motherfucks", - "muff", - "mutha", - "muthafecker", - "muthafuckker", - "muther", - "mutherfucker", - "numbnuts", - "nutsack", - "orgasim", - "orgasims", - "orgasm", - "orgasms", - "p0rn", - "pecker", - "penis", - "penisfucker", - "phonesex", - "phuck", - "phuk", - "phuked", - "phuking", - "phukked", - "phukking", - "phuks", - "phuq", - "pigfucker", - "pimpis", - "porno", - "pornography", - "pornos", - "prick", - "pricks", - "pron", - "pube", - "pusse", - "pussi", - "pussies", - "pussy", - "pussys", - "rectum", - "retard", - "rimjaw", - "rimming", - "s hit", - "s.o.b.", - "screwing", - "scroat", - "scrote", - "scrotum", - "semen", - "sex", - "sh!+", - "sh!t", - "sh1t", - "shag", - "shagger", - "shaggin", - "shagging", - "shemale", - "shi+", - "shit", - "shitdick", - "shite", - "shited", - "shitey", - "shitfuck", - "shitfull", - "shithead", - "shiting", - "shitings", - "shits", - "shitted", - "shitter", - "shitters", - "shitting", - "shittings", - "shitty", - "slut", - "sluts", - "smegma", - "smut", - "son-of-a-bitch", - "s_h_i_t", - "t1tt1e5", - "t1tties", - "teets", - "testical", - "testicle", - "tit", - "titfuck", - "tits", - "titt", - "tittie5", - "tittiefucker", - "titties", - "tittyfuck", - "tittywank", - "titwank", - "tosser", - "turd", - "tw4t", - "twat", - "twathead", - "twatty", - "twunt", - "twunter", - "v14gra", - "v1gra", - "vagina", - "viagra", - "vulva", - "w00se", - "wang", - "wank", - "wanker", - "wanky", - "whoar", - "whore", - "willies", - "willy", - "xrated" - ] -} diff --git a/src/Utils/JSON/emoji.json b/src/Utils/JSON/emoji.json deleted file mode 100644 index ef58b52c..00000000 --- a/src/Utils/JSON/emoji.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "normal": { - "yes": "<:yes:977600103206498354>", - "neutral": "<:neutral:977600034055016499>", - "no": "<:no:977600033740431410>", - "discordStaff": "<:badge_InterChatStaff:1102229356379648011>", - "invite": "<:add:1032318201134067722>", - "clipart": "<:chat_clipart:772393314413707274>", - "checkGreen": "", - "tada": "", - "shinyStaff": "", - "enabled": " <:enabled:991180941311619205>", - "disabled": "<:disabled:991180939122180146>", - "ID": "<:blurple_id:1032318192741261393>", - "loading": "", - "bruhcat": "<:bruhcat:976368249858056223>", - "tick": "<:tick:1032318190144995399>", - "force_enabled": "<:devportal_enabled:994444521888821338>", - "chatbot_circle": "<:chatbot_circle:1077200892157243503>", - "dotYellow": "<:yellowdot:986617776196190248>", - "dotRed": "<:reddot:986617774006738944>", - "dotBlue": "<:dotBlue:1086649062775865364>", - "webhook": "<:webhook:1065979539320217621>", - "online_anim": "", - "idle_anim": "", - "dnd_anim": "", - "offline_anim": "", - "onlineMobile": "<:onlinemobile:715050614429712384>", - "dndMobile": "<:dndmobile:715050614047899741>", - "idleMobile": "<:idlemobile:715050614278717500>", - "description": "<:description:1035140306414342164>", - "slash": "<:slash:1049191025903685733>", - "reply": "<:reply:1064781138477985792>", - "blobFastBan": "", - "back": "<:back:1082924285674401852>", - "forward": "<:forward:1082924287922536508>", - "dividerEnd": "<:divider_end:1077200586803527690>", - "contextMenu": "<:ContextMenuCommand:1086624007949930597>", - "slashCommand": "<:slashCommand:1086631449337266276>", - "blueLine": "<:blueLine:1086634970539360296>", - "attachment": "<:attachment:1102464803647275028>", - "interchatLogoOld": "<:interchat:1155133495270703135>", - "interchatCircle": "<:InterChat:1156787716130869269>" - }, - - - "icons": { - "bot": "<:icons_robot:1032485570162720888>", - "comment": "<:comment:983962459406733312>", - "colorstaff": "<:icons_colorstaff:978937000021332009>", - "join": "<:join:990488359279419443>", - "leave": "<:leave:990488356553097236>", - "mention": "<:mention:991909883903475822>", - "id": "<:id:991912629985628240>", - "owner": "<:owner:992026201478680596>", - "next": "<:next:983284968992169984>", - "back": "<:back:983284970804113478>", - "delete": "<:delete:983284966983082014>", - "store": "<:icons_store:978937006094712842>", - "exclamation": "<:icons_exclamation:978937002760237056>", - "info": "<:info:1032318204023947414>", - "settings": "<:settings:1032320777963450388>", - "search": "<:search:1032320775635619920>", - "activities": "<:activity:1032320780807192626>", - "wand": "<:wand:1032320783386685440>", - "connect": "<:connect:1065977170788352110>", - "disconnect": "<:disconnect:1065977693075673160>", - "members": "<:members:1032318206964146246>", - "staff": "<:staff:983962462380511282>", - "botdev": "<:botdev2:983962441329307659>", - "link": "<:link:1032318209879179295>", - "timeout": "<:timeout:1088474245908152461>" - }, - - "mascot": { - "cry": "<:chipi_cry:1065863977957072977>", - "flushed": "<:chipi_flushed:1065863982679871580>", - "forwn": "<:chipi_frown:1065864007187189760>", - "huh": "<:chipi_huh:1065863996244246559>", - "neutral": "<:chipi_neutral:1065864014942441493>", - "rage": "<:chipi_rage:1065864002405683210>", - "smile": "<:chipi_smile:1065867304480559144>", - "ugh": "<:chipi_ugh:1065863991815049316>" - }, - - "badge": { - "Developer": "<:badge_developer:994549313013284904>", - "Staff": "<:badge_InterChatStaff:1102229356379648011>", - "Premium": "<:badge_BugHunter3:994262351266185317>", - "Voter": "<:topgg_voter:1065977698058506290>", - "Moderator": "<:badge_CertifiedModerator:994444514989182986>", - "BugHunter": "<:badge_BugHunter31:994445137818169416>", - "Christmas2022": "<:Christmas2022:1056214953666814002>" - } -} diff --git a/src/Utils/blacklist.ts b/src/Utils/blacklist.ts deleted file mode 100644 index 744a7e84..00000000 --- a/src/Utils/blacklist.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { cancelJob, scheduleJob } from 'node-schedule'; -import { modActions } from '../Scripts/networkLogs/modActions'; -import { constants, getDb } from './utils'; -import { User, EmbedBuilder, Client, TextBasedChannel } from 'discord.js'; -import emojis from './JSON/emoji.json'; -import { blacklistedServers, blacklistedUsers } from '@prisma/client'; - - -export async function fetchUserBlacklist(hubId: string, userId: string) { - const db = getDb(); - const userBlacklisted = await db.blacklistedUsers.findFirst({ - where: { userId, hubs: { some: { hubId } } }, - }); - return userBlacklisted; -} - -export async function fetchServerBlacklist(hubId: string, serverId: string) { - const db = getDb(); - const userBlacklisted = await db.blacklistedServers.findFirst({ - where: { serverId, hubs: { some: { hubId } } }, - }); - return userBlacklisted; -} - -export async function addUserBlacklist(hubId: string, moderator: User, user: User | string, reason: string, expires?: Date | number) { - if (typeof user === 'string') user = await moderator.client.users.fetch(user); - if (typeof expires === 'number') expires = new Date(Date.now() + expires); - - const db = getDb(); - const dbUser = await db.blacklistedUsers.findFirst({ where: { userId: user.id } }); - - const hubs = dbUser?.hubs.filter((i) => i.hubId !== hubId) || []; - hubs?.push({ expires: expires || null, reason, hubId }); - - const updatedUser = await db.blacklistedUsers.upsert({ - where: { - userId: user.id, - }, - update: { - username: user.username, - hubs: { set: hubs }, - }, - create: { - userId: user.id, - username: user.username, - hubs, - }, - }); - - // Send action to logs channel - modActions(moderator, { - user, - action: 'blacklistUser', - expires, - reason, - }).catch(() => null); - - return updatedUser; -} - -export async function addServerBlacklist(serverId: string, moderator: User, hubId: string, reason: string, expires?: Date) { - const guild = await moderator.client.guilds.fetch(serverId); - const db = getDb(); - - const dbGuild = await db.blacklistedServers.upsert({ - where: { - serverId: guild.id, - }, - update: { - serverName: guild.name, - hubs: { push: { hubId, expires, reason } }, - }, - create: { - serverId: guild.id, - serverName: guild.name, - hubs: [{ hubId, expires, reason }], - }, - }); - - // Send action to logs channel - modActions(moderator, { - guild: { id: guild.id, resolved: guild }, - action: 'blacklistServer', - expires: expires, - reason: reason, - }).catch(() => null); - - return dbGuild; -} - -export async function removeBlacklist(type: 'user', hubId: string, userId: string): Promise -export async function removeBlacklist(type: 'server', hubId: string, serverId: string): Promise -export async function removeBlacklist(type: string, hubId: string, userOrServerId: string) { - const db = getDb(); - cancelJob(`blacklist_${type}-${userOrServerId}`); - - - if (type === 'user') { - return await db.blacklistedUsers.update({ - where: { - userId: userOrServerId, - hubs: { some: { hubId } }, - }, - data: { - hubs: { deleteMany: { where: { hubId } } }, - }, - }); - } - else { - return db.blacklistedServers.update({ - where: { - serverId: userOrServerId, - hubs: { some: { hubId } }, - }, - data: { - hubs: { deleteMany: { where: { hubId } } }, - }, - }); - } -} - - -export function scheduleUnblacklist(type: 'user' | 'server', client: Client, userOrServerId: string, hubId: string, expires: Date | number) { - if (type === 'server') { - return scheduleJob(`blacklist_server-${userOrServerId}`, expires, async function() { - const db = getDb(); - const dbServer = await db.blacklistedServers.findFirst({ - where: { serverId: userOrServerId, hubs: { some: { hubId } } }, - }); - - // only call .delete if the document exists - // or prisma will error - if (dbServer) { - await removeBlacklist('server', hubId, userOrServerId); - - modActions(client.user, { - action: 'unblacklistServer', - oldBlacklist: dbServer, - timestamp: new Date(), - hubId, - }).catch(() => null); - } - }); - } - - if (type === 'user') { - return scheduleJob(`blacklist_user-${userOrServerId}`, expires, async () => { - const db = getDb(); - const dbUser = await db.blacklistedUsers.findFirst({ - where: { userId: userOrServerId, hubs: { some: { hubId } } }, - }); - - const user = await client.users.fetch(userOrServerId).catch(() => null); - if (!user || !dbUser) return; - - await removeBlacklist('user', hubId, userOrServerId); - - modActions(client.user, { - user, - hubId, - action: 'unblacklistUser', - blacklistedFor: dbUser.hubs.find(h => h.hubId === hubId)?.reason, - }).catch(() => null); - }); - } -} - -export async function notifyBlacklist(userOrChannel: User | TextBasedChannel, hubId: string, expires?: Date, reason: string = 'No reason provided.') { - const db = getDb(); - const hub = await db.hubs.findUnique({ where: { id: hubId } }); - const expireString = expires ? `` : 'Never'; - - if (userOrChannel instanceof User) { - const embed = new EmbedBuilder() - .setTitle(emojis.normal.blobFastBan + ' Blacklist Notification') - .setDescription(`You have been blacklisted from talking in hub **${hub?.name}**.`) - .setColor(constants.colors.interchatBlue) - .setFields( - { name: 'Reason', value: reason, inline: true }, - { name: 'Expires', value: expireString, inline: true }, - ); - - userOrChannel.send({ embeds: [embed] }).catch(async () => null); - } - else if (userOrChannel.isTextBased()) { - const embed = new EmbedBuilder() - .setTitle(emojis.normal.blobFastBan + ' Blacklist Notification') - .setDescription(`This server has been blacklisted from talking in hub **${hub?.name}**.`) - .setColor(constants.colors.interchatBlue) - .setFields( - { name: 'Reason', value: reason, inline: true }, - { name: 'Expires', value: expireString, inline: true }, - ); - - await userOrChannel.send({ embeds: [embed] }).catch(() => null); - } - -} \ No newline at end of file diff --git a/src/Utils/deploy-commands.ts b/src/Utils/deploy-commands.ts deleted file mode 100644 index 893301dc..00000000 --- a/src/Utils/deploy-commands.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { REST } from '@discordjs/rest'; -import { RESTPostAPIApplicationCommandsJSONBody, RESTPostAPIApplicationGuildCommandsJSONBody, Routes } from 'discord.js'; -import { stripIndent } from 'common-tags'; -import { constants } from './utils'; - -import fs from 'fs'; -import logger from './logger'; -import 'dotenv/config'; - -const clientID = process.env.CLIENT_ID as string; -const server = process.argv[3]?.toLowerCase() || constants.guilds.cbhq; -const staffCommands = ['Developer', 'Staff']; -const commandsPath = 'build/Commands'; - -const rest = new REST({ version: '10' }).setToken(process.env.TOKEN as string); - -function deployCommands(staff = false) { - const commands: RESTPostAPIApplicationCommandsJSONBody | RESTPostAPIApplicationGuildCommandsJSONBody[] = []; - - fs.readdirSync(commandsPath).forEach((dir) => { - // Only proceed if dir is inside staffCommands array (for deploying only staff commands) - if (staff && !staffCommands.includes(dir)) return; - else if (!staff && staffCommands.includes(dir)) return; - - if (fs.statSync(`${commandsPath}/${dir}`).isDirectory()) { - const commandFiles = fs.readdirSync(`${commandsPath}/${dir}`).filter(file => file.endsWith('.js')); - - for (const commandFile of commandFiles) { - const command = require(`../Commands/${dir}/${commandFile}`); - commands.push(command.default.data.toJSON()); - } - } - }); - - rest.put(staff ? Routes.applicationGuildCommands(clientID, server) : Routes.applicationCommands(clientID), { body: commands }) - .then(() => logger.info(`Registered all ${staff ? 'public' : 'staff'} commands successfully.`)) - .catch(logger.error); -} - -// parse command line args to determine which type of deploy -const args = process.argv[2]?.toLowerCase(); - -switch (args) { - case undefined: - deployCommands(); - break; - - case '--staff': - case '-s': - deployCommands(true); - break; - - case '-b': - case '--both': - deployCommands(); - deployCommands(true); - break; - - case '--help': - case '-help': - case '--h': - case '-h': - logger.info(stripIndent` - Deploy Application Commands - - Usage: - deploy [--staff | -s] [guildId] - deploy [--both | -b] [guildId] - deploy [--help | -help | --h | -h] - Options: - -h, --help Show this help message and exit. - -s, --staff Deploy staff commands. - -b --both Deploy both staff and normal commands. - [guildId] - The guild ID to deploy to.`); - break; - - default: - logger.error('Invalid argument provided. Please use \u001B[40;5;31mdeploy --help\u001B[0m for more information.'); - break; -} diff --git a/src/Utils/errorHandler.ts b/src/Utils/errorHandler.ts deleted file mode 100644 index 3160aaf4..00000000 --- a/src/Utils/errorHandler.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { DiscordAPIError } from 'discord.js'; - - -export function formatErrorCode(error: any) { - let errorMessage = 'An unexpected error occurred while processing your request.'; - - if (error instanceof DiscordAPIError) { - switch (error.code) { - case 50001: - errorMessage = 'I do not have permission to do that.'; - break; - case 50013: - errorMessage = 'You do not have permission to do that.'; - break; - case 50034: - errorMessage = 'I am unable to purge messages older than 14 days.'; - break; - - default: - break; - } - } - - return errorMessage; -} diff --git a/src/Utils/network.ts b/src/Utils/network.ts deleted file mode 100644 index fee89f87..00000000 --- a/src/Utils/network.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { connectedList, hubs } from '@prisma/client'; -import { getDb, getOrCreateWebhook } from './utils'; -import { Guild, TextChannel, ThreadChannel, WebhookClient, WebhookMessageCreateOptions } from 'discord.js'; -import { stripIndents } from 'common-tags'; -import emojis from './JSON/emoji.json'; - -const { connectedList } = getDb(); - -/** Reconnect a channel from the main network.*/ -export async function reconnect(channelId: string) { - const channelExists = await connectedList.findFirst({ - where: { channelId }, - }); - - if (channelExists?.connected === false) { - return await connectedList.update({ - where: { channelId: channelExists.channelId }, - data: { connected: true }, - }); - } - return channelExists; -} - -/** Disconnect a channel from the main network.*/ -export async function disconnect(channelId: string) { - const channelExists = await connectedList.findFirst({ where: { channelId } }); - if (channelExists?.connected) { - return await connectedList.update({ - where: { channelId: channelExists.channelId }, - data: { connected: false }, - }); - } - return channelExists; -} - -export async function sendInNetwork(message: WebhookMessageCreateOptions, hubId: string) { - if (typeof message !== 'string' && !message.username && !message.avatarURL) { - message.username = 'InterChat Network'; - message.avatarURL = 'https://i.imgur.com/jHHkSrf.png'; - } - - const channels = await getDb().connectedList.findMany({ where: { hubId, connected: true } }); - - channels?.forEach(async (channelEntry) => { - const webhookClient = new WebhookClient({ url: channelEntry.webhookURL }); - await webhookClient.send(channelEntry.parentId ? { ...message, threadId: channelEntry.channelId } : message); - }); -} - -export async function createConnection(guild: Guild, hub: hubs, networkChannel: TextChannel | ThreadChannel) { - const webhook = await getOrCreateWebhook(networkChannel, guild.client.user?.displayAvatarURL()); - if (!webhook) return; - - const emoji = emojis.normal; - const createdConnection = await connectedList.create({ - data: { - channelId: networkChannel.id, - parentId: networkChannel.isThread() ? networkChannel.id : undefined, - serverId: networkChannel.guild.id, - webhookURL: webhook.url, - connected: true, - profFilter: true, - compact: false, - hub: { connect: { id: hub.id } }, - }, - }); - - - const numOfConnections = await connectedList.count({ where: { hubId: hub.id } }); - await networkChannel?.send( - `This channel has been connected with **${hub.name}**. ${ - numOfConnections > 1 - ? `You are currently with ${numOfConnections - 1} other servers, Enjoy! ${emoji.clipart}` - : `It seems no one else is there currently... *cricket noises* ${emoji.clipart}` - }`, - ); - - sendInNetwork({ - content: stripIndents` - A new server has joined us! ${emoji.clipart} - - **Server Name:** __${guild.name}__ - **Member Count:** __${guild.memberCount}__ - ` }, hub.id).catch(() => null); - - // return the created connection so we can use it in the next step - return createdConnection; -} - -export default { reconnect, disconnect, sendInNetwork, createConnection, getOrCreateWebhook }; diff --git a/src/Utils/timers.ts b/src/Utils/timers.ts deleted file mode 100644 index 5edb177e..00000000 --- a/src/Utils/timers.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Client } from 'discord.js'; -import { scheduleJob } from 'node-schedule'; -import { modActions } from '../Scripts/networkLogs/modActions'; -import { getDb } from './utils'; -import { removeBlacklist } from './blacklist'; - -/** A function to start timers for blacklist expiry, messageData cleanup, etc. */ -export default async function startTimers(client: Client) { - const db = getDb(); - - // Delete all documents that are older than 24 hours old. - scheduleJob('invite/messageExpired', { hour: 1 }, async () => { - const olderThan1h = new Date(Date.now() - 60 * 60 * 1_000); - await db.hubInvites - .deleteMany({ where: { expires: { lte: olderThan1h } } }) - .catch(() => null); - - const olderThan24h = new Date(Date.now() - 60 * 60 * 24_000); - await db.messageData - .deleteMany({ where: { timestamp: { lte: olderThan24h } } }) - .catch(() => null); - }); - - const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; - const blacklistedServers = await db.blacklistedServers.findMany(query); - const blacklistedUsers = await db.blacklistedUsers.findMany(query); - - // timer to unblacklist servers - blacklistedServers.forEach(async (blacklist) => { - blacklist.hubs.forEach(({ hubId, expires }) => { - if (!expires) return; - - removeBlacklist('server', hubId, blacklist.serverId); - - if (expires < new Date()) { - - modActions(client.user, { - action: 'unblacklistServer', - oldBlacklist: blacklist, - timestamp: new Date(), - hubId, - }); - return; - } - - scheduleJob(`blacklist_server-${blacklist.serverId}`, expires, async function() { - await db.blacklistedServers.delete({ where: { id: blacklist.id } }); - - modActions(client.user, { - action: 'unblacklistServer', - oldBlacklist: blacklist, - timestamp: new Date(), - hubId, - }); - }); - - }); - }); - - // timer to unblacklist users - blacklistedUsers.forEach(async (blacklist) => { - blacklist.hubs.forEach(async ({ hubId, expires, reason }) => { - if (!expires) return; - - // if the blacklist has already expired, delete it from the database - if (expires < new Date()) { - await removeBlacklist('user', hubId, blacklist.userId); - const user = await client.users.fetch(blacklist.userId).catch(() => null); - - if (!user) return; - - modActions(client.user, { action: 'unblacklistUser', user, hubId, blacklistedFor: reason }); - return; - } - - // if the blacklist has not expired, schedule a new job to unblacklist the user - scheduleJob(`blacklist-${blacklist.userId}`, expires, async function(job_blacklist: typeof blacklist) { - const user = await client.users.fetch(job_blacklist.userId).catch(() => null); - await db.blacklistedUsers.delete({ where: { userId: job_blacklist.userId } }); - - if (!user) return; - - modActions(user.client.user, { - action: 'unblacklistUser', - blacklistedFor: reason, - user, - hubId, - }); - }.bind(null, blacklist)); - }); - }); -} \ No newline at end of file diff --git a/src/Utils/translate.ts b/src/Utils/translate.ts deleted file mode 100644 index f1cbcf72..00000000 --- a/src/Utils/translate.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { GoogleTranslator } from '@translate-tools/core/translators/GoogleTranslator'; -import { Scheduler } from '@translate-tools/core/scheduling/Scheduler'; - -/** Do not use this directly, use scheduler instead to avoid rate limit */ -export const translator = new GoogleTranslator(); -export const scheduler = new Scheduler(translator, { translatePoolDelay: 100 }); - -export async function translateText(text: string, to: string, from = 'auto') { - return await scheduler.translate(text, from, to, { directTranslate: true }); -} - diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts deleted file mode 100644 index d89da72d..00000000 --- a/src/Utils/utils.ts +++ /dev/null @@ -1,235 +0,0 @@ -import startCase from 'lodash/startCase'; -import toLower from 'lodash/toLower'; -import logger from './logger'; -import discord from 'discord.js'; -import { Api } from '@top-gg/sdk'; -import { badge } from './JSON/emoji.json'; -import { stripIndents } from 'common-tags'; -import { PrismaClient } from '@prisma/client'; -import { hubs } from '@prisma/client'; -import 'dotenv/config'; - -export const constants = { - developers: ['828492978716409856', '701727675311587358', '456961943505338369'], - staff: ['597265261665714186', '442653948630007808', '689082827979227160'], - guilds: { cbhq: '770256165300338709' }, - - channel: { - bugs: '1035135196053393418', - networklogs: '1156144879869632553', - modlogs: '1042265633896796231', - reports: '1158773603551162398', - goal: '906460473065615403', - suggestions: '1021256657528954900', - }, - colors: { - all: [ - 'Default', - 'White', - 'Aqua', - 'Green', - 'Blue', - 'Yellow', - 'Purple', - 'LuminousVividPink', - 'Fuchsia', - 'Gold', - 'Orange', - 'Red', - 'Grey', - 'DarkNavy', - 'DarkAqua', - 'DarkGreen', - 'DarkBlue', - 'DarkPurple', - 'DarkVividPink', - 'DarkGold', - 'DarkOrange', - 'DarkRed', - 'DarkGrey', - 'DarkerGrey', - 'LightGrey', - 'DarkNavy', - 'Blurple', - 'Greyple', - 'DarkButNotBlack', - 'NotQuiteBlack', - 'Random', - ] as (keyof typeof discord.Colors)[], - interchatBlue: '#5CB5F9' as discord.HexColorString, - invisible: '#2F3136' as discord.HexColorString, - christmas:['#00B32C', '#D6001C', '#FFFFFF'] as discord.HexColorString[], - }, -} as const; - -export const topgg = new Api(process.env.TOPGG as string); -const _prisma = new PrismaClient(); - -export const rulesEmbed = new discord.EmbedBuilder() - .setColor(constants.colors.interchatBlue) - .setImage('https://i.imgur.com/D2pYagc.png') - .setDescription(stripIndents` - ### 📜 InterChat Network Rules - - 1. **Use Common Sense:** Be considerate of others and their views. No slurs, derogatory language or any actions that can disrupt the chat's comfort. - 2. **No Spamming or Flooding:** Avoid repeated, nonsensical, or overly lengthy messages. - 3. **Keep Private Matters Private:** Avoid sharing personal information across the network. - 4. **No Harassment:** Trolling, insults, or harassment of any kind are not tolerated. - 5. **No NSFW/NSFL Content:** Posting explicit NSFW/NSFL content will result in immediate blacklist. - 6. **Respect Sensitive Topics:** Do not trivialize self-harm, suicide, violence, or other offensive topics. - 7. **Report Concerns:** If you observe a violation of these rules, report it to the appropriate hub moderator or InterChat staff for further action. - - Any questions? Join our [support server](https://discord.gg/6bhXQynAPs). - `, - ); - -export function replaceLinks(string: string, replaceText = '`[LINK HIDDEN]`') { - const urlRegex = /https?:\/\/(?!tenor\.com|giphy\.com)\S+/g; - return string.replaceAll(urlRegex, replaceText); -} - -export function yesOrNoEmoji(option: unknown, yesEmoji: string, noEmoji: string) { - return option ? yesEmoji : noEmoji; -} - -export function toTitleCase(txt: string): string { - return startCase(toLower(txt)); -} - -export async function getHubName(hubId: string) { - return (await getDb().hubs.findUnique({ where: { id: hubId } }))?.name; -} - -export function getGuildName(client: discord.Client, gid: string | null) { - if (!gid) return ''; - return client.guilds.cache.get(gid)?.name; -} - -/** Return a random element from an array */ -export function choice(arr: T[]) { - return arr[Math.floor(Math.random() * arr.length)]; -} - -/** Send a message to the first text based channel in a guild */ -export async function sendInFirst(guild: discord.Guild, message: string | discord.MessagePayload | discord.BaseMessageOptions) { - const channels = await guild.channels.fetch(); - - const channel = channels.find(chn => - chn?.isTextBased() && - chn.permissionsFor(guild.members.me as discord.GuildMember).has('SendMessages')) as discord.GuildTextBasedChannel | null | undefined; - - await channel?.send(message).catch((e) => !e.message.includes('Missing Access') || !e.message.includes('Missing Permissions') ? logger.error(e) : null); -} - -export async function getCredits() { - let creditArray: string[] = []; - - creditArray = creditArray.concat( - constants.developers, - constants.staff, - ); - - // Exiryn (Mascot Artist) - creditArray.push('880978672037802014'); - - return creditArray; -} - -/** Use the main database in your code by calling this function */ -export function getDb() { - return _prisma; -} - -/** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ -export function toHuman(milliseconds: number): string { - let totalSeconds = milliseconds / 1000; - const days = Math.floor(totalSeconds / 86400); - totalSeconds %= 86400; - const hours = Math.floor(totalSeconds / 3600); - totalSeconds %= 3600; - const minutes = Math.floor(totalSeconds / 60); - const seconds = Math.floor(totalSeconds % 60); - let readable; - - if (days == 0 && hours == 0 && minutes == 0) readable = `${seconds} seconds`; - else if (days == 0 && hours == 0) readable = `${minutes}m ${seconds}s`; - else if (days == 0) readable = `${hours}h, ${minutes}m ${seconds}s`; - else readable = `${days}d ${hours}h, ${minutes}m ${seconds}s`; - - return readable; -} - -/** - * Checks if a user is a InterChat Staff or Developer - * @param onlyDeveloper Only check if user is a developer - */ -export function checkIfStaff(userId: string, onlyDeveloper = false) { - const isStaff = constants.staff.find((uId) => uId == userId); - const isDev = constants.developers.find((uId) => uId == userId); - - if (onlyDeveloper && !isDev) return false; - else if (isStaff || isDev) return true; - return false; -} - -export function badgeToEmoji(badgeArr: string[]) { - const badgeEmojis: string[] = []; - const tempbadge: { [key: string]: string } = badge; - - badgeArr.forEach((badgeName) => { - if (badgeName in tempbadge) badgeEmojis.push(tempbadge[badgeName]); - }); - return badgeEmojis; -} - -export function calculateAverageRating(ratings: number[]): number { - if (ratings.length === 0) return 0; - - const sum = ratings.reduce((acc, cur) => acc + cur, 0); - const average = sum / ratings.length; - return Math.round(average * 10) / 10; -} - -interface HubListingExtraInput { - connections?: number; -} - -export function createHubListingsEmbed(hub: hubs, extra?: HubListingExtraInput) { - return new discord.EmbedBuilder() - .setDescription(stripIndents` - ### ${hub.name} - ${hub.description} - - **Rating:** ${hub.rating?.length > 0 ? '⭐'.repeat(calculateAverageRating(hub.rating.map(hr => hr.rating))) : '-'} - **Connections:** ${extra?.connections ?? 'Unknown.'} - **Created At:** - `) - .setColor('Random') - .setThumbnail(hub.iconUrl) - .setImage(hub.bannerUrl); -} - - -export async function deleteHubs(ids: string[]) { - // delete all relations first and then delete the hub - await _prisma.connectedList.deleteMany({ where: { hubId: { in: ids } } }); - await _prisma.hubInvites.deleteMany({ where: { hubId: { in: ids } } }); - await _prisma.messageData.deleteMany({ where: { hubId: { in: ids } } }); - await _prisma.hubs.deleteMany({ where: { id: { in: ids } } }); -} - -export async function getOrCreateWebhook(channel: discord.TextChannel | discord.ThreadChannel, avatar: string | null) { - const channelOrParent = channel.type === discord.ChannelType.GuildText ? channel : channel.parent; - const webhooks = await channelOrParent?.fetchWebhooks(); - const existingWebhook = webhooks?.find((w) => w.owner?.id === channel.client.user?.id); - - if (existingWebhook) { - return existingWebhook; - } - - return await channelOrParent?.createWebhook({ - name: 'InterChat Network', - avatar, - }); -} - diff --git a/src/Utils/wordFilter.ts b/src/Utils/wordFilter.ts deleted file mode 100644 index 92e3e7dc..00000000 --- a/src/Utils/wordFilter.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { EmbedBuilder, User, TextChannel } from 'discord.js'; -import { constants, getGuildName, getHubName } from './utils'; -import { badwords } from './JSON/badwords.json'; - -/** - * Checks if a message contains any bad words. -*/ -export function check(string: string | undefined) { - if (!string) return false; - return badwords.some(word => string.split(/\b/).some(w => w.toLowerCase() === word.toLowerCase())); -} - -/** - * If the message contains bad words, it will be censored with asterisk(*). - * - * Code refrernced from [`@web-mech/badwords`](https://github.com/web-mech/badwords). -*/ -export function censor(message: string): string { - const splitRegex = /\b/; - const specialChars = /[^a-zA-Z0-9|$|@]|\^/g; - const matchWord = /\w/g; - // filter bad words from message - // and replace it with * - return message.split(splitRegex).map(word => { - return check(word) ? word.replace(specialChars, '').replace(matchWord, '\\*') : word; - }).join(splitRegex.exec(message)?.at(0)); -} - - -/** A function that can be used to send a log of an ***uncensored*** message to the log channel. */ -export async function log(rawContent: string, author: User, guildId: string | null, hubId: string) { - const logChan = await author.client.channels.fetch(constants.channel.networklogs) as TextChannel; - const hubName = await getHubName(hubId).catch(() => 'Unknown'); - const guildName = getGuildName(author.client, guildId); - const logEmbed = new EmbedBuilder() - .setAuthor({ name: `${author.client.user?.username} logs`, iconURL: author.client.user?.avatarURL()?.toString() }) - .setTitle('Bad Word Detected') - .setColor(constants.colors.invisible) - .setDescription(`||${rawContent}||\n\n**Author:** @${author.username} \`(${author.id})\`\n**Server:** ${guildName} (${guildId})\n**Hub:** ${hubName}`); - return await logChan?.send({ embeds: [logEmbed] }); -} - -export default { check, censor, log }; diff --git a/src/commands/Command.ts b/src/commands/Command.ts new file mode 100644 index 00000000..c5f26be9 --- /dev/null +++ b/src/commands/Command.ts @@ -0,0 +1,62 @@ +import { + AutocompleteInteraction, + ChatInputCommandInteraction, + Collection, + ContextMenuCommandInteraction, + MessageComponentInteraction, + ModalSubmitInteraction, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; + +type CommandInteraction = ChatInputCommandInteraction | ContextMenuCommandInteraction; +type InteractionFunction = ( + interaction: MessageComponentInteraction | ModalSubmitInteraction, +) => void; + +export interface CommandOptions { + readonly data: RESTPostAPIApplicationCommandsJSONBody; + readonly staffOnly?: boolean; + readonly description?: string; + readonly execute: (interaction: CommandInteraction) => Promise; + readonly autocomplete?: (interaction: AutocompleteInteraction) => Promise; +} + +export const commandsMap = new Collection(); +export const interactionsMap = new Collection(); + +export default abstract class Command { + abstract readonly data: RESTPostAPIApplicationCommandsJSONBody; + readonly staffOnly?: boolean; + readonly description?: string; + + abstract execute(interaction: CommandInteraction): Promise; + + // optional methods + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handleComponent(interaction: MessageComponentInteraction): void { + /**/ + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handleModal(interaction: ModalSubmitInteraction): void { + /**/ + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + autocomplete(interaction: AutocompleteInteraction): void { + /**/ + } + + loadCommand() { + // Save command to the `clientCommands` map + commandsMap.set(this.data.name, this.toJSON()); + } + + toJSON() { + return { + data: this.data, + staffOnly: this.staffOnly, + description: this.description, + execute: this.execute, + autocomplete: this.autocomplete, + } as CommandOptions; + } +} diff --git a/src/commands/slash/Information/stats.ts b/src/commands/slash/Information/stats.ts new file mode 100644 index 00000000..d4070490 --- /dev/null +++ b/src/commands/slash/Information/stats.ts @@ -0,0 +1,102 @@ +import { + ActionRowBuilder, + ApplicationCommandType, + ButtonBuilder, + ButtonStyle, + ChatInputCommandInteraction, + EmbedBuilder, + Status, +} from 'discord.js'; +import db from '../../../utils/Db.js'; +import Command from '../../Command.js'; +import { cpus, totalmem } from 'os'; +import { colors, isDevBuild } from '../../../utils/Constants.js'; +import { msToReadable } from '../../../utils/Utils.js'; +import { stripIndents } from 'common-tags'; + +export default class Stats extends Command { + readonly data = { + name: 'stats', + description: 'View InterChat\'s statistics.', + type: ApplicationCommandType.ChatInput, + }; + + async execute(interaction: ChatInputCommandInteraction) { + await interaction.deferReply(); + + const { connectedList, messageData, hubs } = db; + const totalConnections = await connectedList?.count(); + const totalHubs = await hubs?.count(); + const totalNetworkMessages = await messageData.count(); + + const count = (await interaction.client.cluster.fetchClientValues( + 'guilds.cache.size', + )) as number[]; + const guildCount = count.reduce((p, n) => p + n, 0); + + const uptime = msToReadable(interaction.client.uptime); + const docsLink = 'https://discord-interchat.github.io/docs'; + const supportServer = 'https://discord.gg/6bhXQynAPs'; + + const embed = new EmbedBuilder() + .setColor(colors.invisible) + .setTitle(`${interaction.client.user.username} Statistics`) + .setFooter({ + text: `InterChat v${interaction.client.version}${isDevBuild ? '+dev' : ''}`, + iconURL: interaction.client.user.displayAvatarURL(), + }) + .addFields([ + { + name: 'Bot Stats', + value: stripIndents` + - Uptime: \`${uptime}\` + - Servers ${guildCount} + - Shards: ${interaction.client.cluster.info.TOTAL_SHARDS} + `, + inline: true, + }, + { + name: 'System Stats', + value: stripIndents` + - OS: Linux + - CPU Cores: ${cpus().length} + - RAM Usage: ${Math.round( + process.memoryUsage().heapUsed / 1024 / 1024, + )} MB / ${Math.round(totalmem() / 1024 / 1024 / 1024)} GB + `, + inline: true, + }, + { name: '\u200B', value: '\u200B', inline: true }, + { + name: 'Shard Stats', + value: stripIndents` + - ID: 1 + - State: ${interaction.guild ? Status[interaction.guild.shard.status] : 'Disconnected'} + - Ping: \`${interaction.guild?.shard.ping}ms\``, + inline: true, + }, + { + name: 'Hub Stats', + value: stripIndents` + - Total Hubs: ${totalHubs} + - Total Connected: ${totalConnections} + - Messages (Today): ${totalNetworkMessages} + `, + inline: true, + }, + { name: '\u200B', value: '\u200B', inline: true }, + ]); + + const linksRow = new ActionRowBuilder().addComponents( + new ButtonBuilder().setLabel('Support').setStyle(ButtonStyle.Link).setURL(supportServer), + new ButtonBuilder().setLabel('Guide').setStyle(ButtonStyle.Link).setURL(docsLink), + new ButtonBuilder().setLabel('Guido').setStyle(ButtonStyle.Primary).setCustomId('guide'), + new ButtonBuilder() + .setLabel('Invite') + .setStyle(ButtonStyle.Link) + .setURL(`https://discord.com/application-directory/${interaction.client.user?.id}`), + ); + + await interaction.editReply({ embeds: [embed], components: [linksRow] }); + } +} diff --git a/src/commands/slash/Main/connection.ts b/src/commands/slash/Main/connection.ts new file mode 100644 index 00000000..5f524ffe --- /dev/null +++ b/src/commands/slash/Main/connection.ts @@ -0,0 +1,307 @@ +import { + ActionRowBuilder, + ApplicationCommandOptionType, + AutocompleteInteraction, + ChannelSelectMenuBuilder, + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + MessageComponentInteraction, + ModalBuilder, + ModalSubmitInteraction, + PermissionFlagsBits, + RESTPostAPIApplicationCommandsJSONBody, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, + TextInputBuilder, + TextInputStyle, +} from 'discord.js'; +import Command from '../../Command.js'; +import db from '../../../utils/Db.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { buildEmbed } from '../../../scripts/network/buildEmbed.js'; +import { buildConnectionButtons } from '../../../scripts/network/components.js'; +import { emojis } from '../../../utils/Constants.js'; +import { CustomID } from '../../../structures/CustomID.js'; + +export default class Connection extends Command { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + name: 'connection', + description: 'Manage your connections in this server.', + default_member_permissions: `${PermissionFlagsBits.ManageMessages}`, + dm_permission: false, + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'connection', + description: 'Choose a connection.', + required: true, + autocomplete: true, + }, + ], + }; + async execute(interaction: ChatInputCommandInteraction): Promise { + const connection = interaction.options.getString('connection', true); + const networkManager = interaction.client.getNetworkManager(); + const isInDb = await networkManager.fetchConnection({ channelId: connection }); + + if (!isInDb) { + interaction.reply({ + content: `${emojis.no} This connection does not exist.`, + ephemeral: true, + }); + return; + } + + const embed = await buildEmbed(interaction, connection); + const buttons = buildConnectionButtons(true, connection, { userId: interaction.user.id }); + + if (!interaction.deferred && !interaction.replied) await interaction.deferReply(); + + const customizeMenu = new ActionRowBuilder().addComponents([ + new StringSelectMenuBuilder() + .setCustomId( + new CustomID() + .setIdentifier('connection', 'settings') + .addData(connection) + .addData(interaction.user.id) + .toString(), + ) + .setPlaceholder('🛠️ Select a setting to toggle') + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel('Compact') + .setEmoji(emojis.clipart) + .setDescription('Disable embeds in the network to fit more messages.') + .setValue('compact'), + new StringSelectMenuOptionBuilder() + .setLabel('Profanity Filter') + .setEmoji('🤬') + .setDescription('Toggle swear word censoring for this server.') + .setValue('profanity'), + new StringSelectMenuOptionBuilder() + .setLabel('Invite Link') + .setEmoji(emojis.members) + .setDescription('Set an invite for network users to join your server easily!') + .setValue('invite'), + new StringSelectMenuOptionBuilder() + .setLabel('Switch Channel') + .setEmoji(emojis.store) + .setDescription('Set a different channel for the network.') + .setValue('change_channel'), + new StringSelectMenuOptionBuilder() + .setLabel('Embed Color') + .setEmoji('🎨') + .setDescription('Set the color of the embeds sent in the network.') + .setValue('embed_color'), + ), + ]); + + const channelExists = await interaction.client.channels.fetch(connection).catch(() => null); + + if (!channelExists) { + await networkManager.updateConnection( + { channelId: connection }, + { connected: !isInDb.connected }, + ); + await interaction.followUp({ + content: `${emojis.no} Automatically disconnected from network due to errors. Change the channel to use the network.`, + ephemeral: true, + }); + } + + await interaction.editReply({ + embeds: [embed], + components: [customizeMenu, buttons], + }); + + // TODO Button expiration + } + + async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + + const isInDb = await db.connectedList.findMany({ + where: { + serverId: interaction.guild?.id, + OR: [ + { channelId: { contains: focusedValue } }, + { hub: { name: { contains: focusedValue } } }, + ], + }, + select: { channelId: true, hub: true }, + take: 25, + }); + + const filtered = isInDb?.map(async ({ channelId, hub }) => { + const channel = await interaction.guild?.channels.fetch(channelId).catch(() => null); + return { name: `${hub?.name} | #${channel?.name || channelId}`, value: channelId }; + }); + + interaction.respond(await Promise.all(filtered)); + } + + @ComponentInteraction('connection') + async handleComponent(interaction: MessageComponentInteraction) { + const customId = CustomID.toJSON(interaction.customId); + const channelId = customId.data[0]; + + if (customId.data.at(1) && customId.data[1] !== interaction.user.id) { + interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor('Red') + .setDescription( + `${emojis.no} This button is not for you. Execute the command yourself to utilize this button.`, + ), + ], + ephemeral: true, + }); + return; + } + + const networkManager = interaction.client.getNetworkManager(); + const isInDb = await networkManager.fetchConnection({ channelId }); + if (!isInDb || !channelId) { + await interaction.reply({ + content: `${emojis.no} This connection does not exist.`, + ephemeral: true, + }); + return; + } + + if (interaction.isButton() && customId.postfix === 'toggle') { + const toggleRes = await networkManager.updateConnection( + { channelId }, + { connected: !isInDb.connected }, + ); + + await interaction.update({ + embeds: [await buildEmbed(interaction, channelId)], + components: [ + interaction.message.components[0], + await buildConnectionButtons(toggleRes?.connected, channelId), + ], + }); + } + else if (interaction.isStringSelectMenu()) { + switch (interaction.values[0]) { + case 'compact': + await networkManager.updateConnection({ channelId }, { compact: !isInDb.compact }); + break; + + case 'profanity': + await networkManager.updateConnection({ channelId }, { profFilter: !isInDb.profFilter }); + break; + + case 'invite': { + const modal = new ModalBuilder() + .setTitle('Add Invite Link') + .setCustomId( + new CustomID() + .setIdentifier('connectionModal', 'invite') + .addData(channelId) + .toString(), + ) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Invite Link') + .setValue('https://discord.gg/') + .setCustomId(new CustomID().setIdentifier('connInviteField').toString()) + .setRequired(false) + .setStyle(TextInputStyle.Short), + ), + ); + + await interaction.showModal(modal); + break; + } + case 'change_channel': { + const channelSelect = new ActionRowBuilder().addComponents( + new ChannelSelectMenuBuilder() + .setCustomId( + new CustomID() + .setIdentifier('connection', 'change_channel') + .addData(channelId) + .addData(interaction.user.id) + .toString(), + ) + .setChannelTypes( + ChannelType.GuildText, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ) + .setPlaceholder('Select a channel to switch to.'), + ); + + await interaction.reply({ + content: `${emojis.info} Select a channel to switch to using the select menu below:`, + components: [channelSelect], + ephemeral: true, + }); + break; + } + + case 'embed_color': { + // TODO + break; + } + + default: + break; + } + + const newEmbeds = await buildEmbed(interaction, channelId); + interaction.replied || interaction.deferred + ? await interaction.editReply({ embeds: [newEmbeds] }) + : await interaction.update({ embeds: [newEmbeds] }); + } + else if (interaction.isChannelSelectMenu()) { + if (customId.postfix !== 'change_channel') return; + const newChannel = interaction.channels.first(); + + if (newChannel?.id === channelId) { + await interaction.reply({ + content: `${emojis.no} You cannot switch to the same channel.`, + ephemeral: true, + }); + return; + } + + await networkManager.updateConnection({ channelId }, { channelId: newChannel?.id }); + + await interaction.update(`${emojis.yes} Switched network channel to <#${newChannel?.id}>.`); + } + } + + @ComponentInteraction('connectionModal') + async handleModal(interaction: ModalSubmitInteraction): Promise { + const customId = CustomID.toJSON(interaction.customId); + if (customId.identifier !== 'connectionModal') return; + + const invite = interaction.fields.getTextInputValue('connInviteField'); + const channelId = customId.data[0]; + const networkManager = interaction.client.getNetworkManager(); + + if (!invite) { + await networkManager.updateConnection({ channelId }, { invite: { unset: true } }); + await interaction.reply({ content: `${emojis.yes} Invite Removed.`, ephemeral: true }); + return; + } + + const isValid = await interaction.client?.fetchInvite(invite).catch(() => null); + + if (isValid?.guild?.id !== interaction.guildId) { + await interaction.reply({ content: `${emojis.no} Invalid Invite.`, ephemeral: true }); + return; + } + + await networkManager.updateConnection({ channelId }, { invite }); + + await interaction.reply({ + content: `${emojis.yes} Invite Added. Others can now join the server by using \`Message Info\` Apps command in the network.`, + ephemeral: true, + }); + } +} diff --git a/src/commands/slash/Main/hub.ts b/src/commands/slash/Main/hub.ts new file mode 100644 index 00000000..3080a978 --- /dev/null +++ b/src/commands/slash/Main/hub.ts @@ -0,0 +1,185 @@ +import { + APIApplicationCommandBasicOption, + ActionRowBuilder, + ApplicationCommandOptionType, + AutocompleteInteraction, + ButtonBuilder, + ButtonStyle, + ChatInputCommandInteraction, + MessageComponentInteraction, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import Command from '../../Command.js'; +import db from '../../../utils/Db.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; + +const hubOption: APIApplicationCommandBasicOption = { + name: 'hub', + description: 'Choose a hub.', + required: true, + type: ApplicationCommandOptionType.String, + autocomplete: true, +}; + +export default class Hub extends Command { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + name: 'hub', + description: 'Manage your hubs.', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'browse', + description: '🔍 Browse public hubs and join them!', + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'edit', + description: '📝 Edit a hub you own.', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'settings', + description: '⚙️ Edit your hub settings', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'join', + description: '🔗 Join a public/private hub from this server.', + options: [ + hubOption, + { + type: ApplicationCommandOptionType.String, + name: 'invite', + description: 'The invite code of the private hub you want to join.', + required: false, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'leave', + description: '👋 Leave a hub from this server.', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'create', + description: '✨ Create a new hub.', + options: [ + { + name: 'name', + description: 'A name for your hub.', + required: true, + type: ApplicationCommandOptionType.String, + }, + { + name: 'icon', + description: 'The icon of your hub. Use a valid i.imgur.com link.', + type: ApplicationCommandOptionType.String, + }, + { + name: 'banner', + description: 'The banner of your hub. Use a valid i.imgur.com link.', + type: ApplicationCommandOptionType.String, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'delete', + description: '🗑️ Delete a hub you own.', + options: [hubOption], + }, + ], + }; + + async execute(interaction: ChatInputCommandInteraction): Promise { + await interaction.reply({ + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub', 'browse') + .toString(), + ) + .setLabel('Browse') + .setStyle(ButtonStyle.Primary), + ), + ], + }); + return; + } + + @ComponentInteraction('hub:browse') + async handleComponent(interaction: MessageComponentInteraction) { + interaction.reply('hello world!'); + } + + async autocomplete(interaction: AutocompleteInteraction): Promise { + const modCmds = ['manage', 'settings', 'connections', 'invite', 'moderator']; + + const subcommand = interaction.options.getSubcommand(); + const subcommandGroup = interaction.options.getSubcommandGroup(); + const focusedValue = interaction.options.getFocused(); + let hubChoices; + + if (subcommand === 'browse' || subcommand === 'join') { + hubChoices = await db.hubs.findMany({ + where: { + name: { mode: 'insensitive', contains: focusedValue }, + private: false, + }, + take: 25, + }); + } + else if (modCmds.includes(subcommandGroup || subcommand)) { + hubChoices = await db.hubs.findMany({ + where: { + name: { mode: 'insensitive', contains: focusedValue }, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + take: 25, + }); + } + else if (subcommand === 'leave') { + const networks = await db.connectedList.findMany({ + where: { serverId: interaction.guild?.id }, + select: { channelId: true, hub: true }, + take: 25, + }); + + const filteredNets = networks + .filter((network) => network.hub?.name.toLowerCase().includes(focusedValue.toLowerCase())) + .map(async (network) => { + const channel = await interaction.guild?.channels + .fetch(network.channelId) + .catch(() => null); + return { + name: `${network.hub?.name} | #${channel?.name ?? network.channelId}`, + value: network.channelId, + }; + }); + + return await interaction.respond(await Promise.all(filteredNets)); + } + else if (subcommand === 'delete') { + hubChoices = await db.hubs.findMany({ + where: { + ownerId: interaction.user.id, + name: { mode: 'insensitive', contains: focusedValue }, + }, + take: 25, + }); + } + + const filtered = hubChoices?.map((hub) => ({ name: hub.name, value: hub.name })); + filtered ? await interaction.respond(filtered) : null; + } +} diff --git a/src/decorators/Interaction.ts b/src/decorators/Interaction.ts new file mode 100644 index 00000000..3791edad --- /dev/null +++ b/src/decorators/Interaction.ts @@ -0,0 +1,14 @@ +import { interactionsMap } from '../commands/Command.js'; + +// Decorator function to call a specified method on interactionCreate +export function ComponentInteraction(customId: string): MethodDecorator { + return function( + targetClass: Record, + propertyKey: string | symbol, + descriptor: PropertyDescriptor, + ) { + const originalMethod = descriptor.value; + interactionsMap.set(customId, originalMethod); + }; +} + diff --git a/src/index.ts b/src/index.ts index cdad03b4..17b99543 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,74 +1,96 @@ -import fs from 'fs'; -import * as Sentry from '@sentry/node'; -import { Client, Collection, ActivityType, ClientOptions } from 'discord.js'; +import db from './utils/Db.js'; +import Logger from './utils/Logger.js'; +import { ClusterManager } from 'discord-hybrid-sharding'; +import { updateTopGGStats } from './updater/StatsUpdater.js'; +import { isDevBuild } from './utils/Constants.js'; +import { Scheduler } from './structures/Scheduler.js'; +import { blacklistedServers, blacklistedUsers } from '@prisma/client'; +import { BlacklistManager } from './structures/BlacklistManager.js'; +import { wait } from './utils/Utils.js'; import 'dotenv/config'; -export class ExtendedClient extends Client { - constructor(options: ClientOptions) { - super(options); - - this.commands = new Collection(); - this.commandCooldowns = new Collection(); - this.reactionCooldowns = new Collection(); - this.description = 'A growing Discord bot which provides inter-server chat! https://discord-interchat.github.io/'; - this.version = '3.13.0'; - } - - public async start(token?: string) { - this.loadCommands(); - this.loadEvents(); +const manager = new ClusterManager('build/InterChat.js', { + totalShards: 1, + mode: 'process', + token: process.env.TOKEN, + shardsPerClusters: 1, + shardArgs: [`--production=${isDevBuild}`], +}); - await this.login(token || process.env.TOKEN); - } +manager.spawn({ timeout: -1 }); - protected loadCommands() { - fs.readdirSync(`${__dirname}/Commands/`).forEach(async (dir: string) => { - if (fs.statSync(`${__dirname}/Commands/${dir}`).isDirectory()) { - const commandFiles = fs.readdirSync(`${__dirname}/Commands/${dir}`) - .filter((file: string) => file.endsWith('.js')); +// other jobs +const syncBotlistStats = async () => { + const count = (await manager.fetchClientValues('guilds.cache.size')) as number[]; + Logger.info( + `Updated top.gg stats with ${count.reduce((p, n) => p + n, 0)} guilds and ${ + manager.totalShards + } shards`, + ); + // update stats + updateTopGGStats( + count.reduce((p, n) => p + n, 0), + manager.totalShards, + ); +}; - for (const commandFile of commandFiles) { - const command = require(`./Commands/${dir}/${commandFile}`); +const deleteExpiredInvites = async () => { + const olderThan1h = new Date(Date.now() - 60 * 60 * 1_000); + await db.hubInvites.deleteMany({ where: { expires: { lte: olderThan1h } } }).catch(() => null); +}; - command.default.directory = dir; - this.commands.set(command.default.data.name, command.default); - } - } - }); - } +const deleteOldMessages = async () => { + // Delete all documents that are older than 24 hours old. + const olderThan24h = new Date(Date.now() - 60 * 60 * 24_000); + await db.messageData + .deleteMany({ where: { timestamp: { lte: olderThan24h } } }) + .catch(() => null); +}; - protected loadEvents() { - const eventFiles = fs.readdirSync(`${__dirname}/Events`).filter((file: string) => file.endsWith('.js')); +const loopThruBlacklists = (blacklists: (blacklistedServers | blacklistedUsers)[], scheduler: Scheduler) => { + if (blacklists.length === 0) return; - for (const eventFile of eventFiles) { - const event = require(`./Events/${eventFile}`); + const blacklistManager = new BlacklistManager(scheduler); + for (const blacklist of blacklists) { + for (const { hubId, expires } of blacklist.hubs) { + if (!expires) continue; - if (event.once) { - this.once(event.default.name, (...args) => event.default.execute(...args, this)); - } - else { - this.on(event.default.name, (...args) => event.default.execute(...args, this)); + if (expires < new Date()) { + if ('serverId' in blacklist) blacklistManager.removeBlacklist('server', hubId, blacklist.serverId); + else blacklistManager.removeBlacklist('user', hubId, blacklist.userId); } + blacklistManager.scheduleRemoval( + 'serverId' in blacklist ? 'server' : 'user', + 'serverId' in blacklist ? blacklist.serverId : blacklist.userId, + hubId, + expires, + ); } } -} +}; -const client = new ExtendedClient({ - intents: ['Guilds', 'GuildMessages', 'GuildMembers', 'MessageContent', 'GuildMessageReactions'], - allowedMentions: { parse: [], repliedUser: true }, - presence: { - status: 'online', - activities: [{ - state: '👀 Watching over 300+ networks... /hub browse', - type: ActivityType.Custom, - name: 'custom', - }], - }, -}); +manager.on('clusterCreate', async (cluster) => { + // last cluster + if (cluster.id === manager.totalClusters - 1 && !isDevBuild) { + // give time for shards to connect + await wait(10_000); + // perform tasks on start up + syncBotlistStats(); + deleteOldMessages(); + deleteExpiredInvites(); -// Error monitoring (sentry.io) -Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 1.0 }); + const scheduler = new Scheduler(); -client.start(); + // update top.gg stats every 10 minutes + scheduler.addTask('syncBotlistStats', 60 * 10_000, syncBotlistStats); // every 10 minutes + scheduler.addTask('deleteExpiredInvites', 60 * 60 * 1_000, deleteExpiredInvites); // every hour + scheduler.addTask('deleteOldMessages', 60 * 60 * 12_000, deleteOldMessages); // every 12 hours + + // remove expired blacklists or set new timers for them + const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; + loopThruBlacklists(await db.blacklistedServers.findMany(query), scheduler); + loopThruBlacklists(await db.blacklistedUsers.findMany(query), scheduler); + } +}); diff --git a/src/scripts/network/buildEmbed.ts b/src/scripts/network/buildEmbed.ts new file mode 100644 index 00000000..192f6551 --- /dev/null +++ b/src/scripts/network/buildEmbed.ts @@ -0,0 +1,30 @@ +import { Interaction, EmbedBuilder } from 'discord.js'; +import { emojis, colors } from '../../utils/Constants.js'; +import { yesOrNoEmoji } from '../../utils/Utils.js'; +import db from '../../utils/Db.js'; + +export async function buildEmbed(interaction: Interaction, channelId: string) { + const networkData = await db.connectedList.findFirst({ where: { channelId }, include: { hub: true } }); + + const { yes, no, enabled, disabled } = emojis; + const invite = networkData?.invite + ? `Code: [\`${networkData.invite}\`](https://discord.gg/${networkData.invite})` + : 'Not Set.'; + + return new EmbedBuilder() + .setTitle('Edit Settings') + .setDescription(`Showing network settings for <#${channelId}>.`) + .addFields([ + { name: 'Channel', value: `<#${channelId}>`, inline: true }, + { name: 'Hub', value: `${networkData?.hub?.name}`, inline: true }, + { name: 'Invite', value: invite, inline: true }, + { name: 'Connected', value: yesOrNoEmoji(networkData?.connected, yes, no), inline: true }, + { name: 'Compact', value: yesOrNoEmoji(networkData?.compact, enabled, disabled), inline: true }, + { name: 'Profanity Filter', value: yesOrNoEmoji(networkData?.profFilter, enabled, disabled), inline: true }, + { name: 'Embed Color', value: networkData?.embedColor ? `\`${networkData?.embedColor}\`` : no, inline: true }, + ]) + .setColor(colors.interchatBlue) + .setThumbnail(interaction.guild?.iconURL() || interaction.client.user.avatarURL()) + .setTimestamp() + .setFooter({ text: 'Use to menu below to edit.' }); +} \ No newline at end of file diff --git a/src/scripts/network/components.ts b/src/scripts/network/components.ts new file mode 100644 index 00000000..f6a82bce --- /dev/null +++ b/src/scripts/network/components.ts @@ -0,0 +1,33 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { CustomID } from '../../structures/CustomID.js'; +import { emojis } from '../../utils/Constants.js'; + +type extraOpts = { + disconnectEmoji?: string; + connectEmoji?: string; + userId?: string; +}; + +/** + * @param channelId The channel ID of the connection. + */ +export function buildConnectionButtons(connected: boolean | undefined, channelId: string, opts: extraOpts = {}) { + if (!opts?.disconnectEmoji || !opts.connectEmoji) { + opts.disconnectEmoji = emojis.disconnect; + opts.connectEmoji = emojis.connect; + } + + return new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setCustomId( + new CustomID() + .setIdentifier('connection', 'toggle') + .addData(channelId) + .addData(opts?.userId ?? '') + .toString(), + ) + .setLabel(connected ? 'Disconnect' : 'Reconnect') + .setStyle(connected ? ButtonStyle.Danger : ButtonStyle.Success) + .setEmoji(connected ? opts.disconnectEmoji : opts.connectEmoji), + ]); +} diff --git a/src/structures/BlacklistManager.ts b/src/structures/BlacklistManager.ts new file mode 100644 index 00000000..736c4d16 --- /dev/null +++ b/src/structures/BlacklistManager.ts @@ -0,0 +1,200 @@ +import db from '../utils/Db.js'; +import Logger from '../utils/Logger.js'; +import SuperClient from '../SuperClient.js'; +import { blacklistedServers, blacklistedUsers } from '@prisma/client'; +import { Scheduler } from './Scheduler.js'; +import { User, TextBasedChannel, EmbedBuilder } from 'discord.js'; +import { emojis, colors } from '../utils/Constants.js'; +import { captureException } from '@sentry/node'; + +export class BlacklistManager { + private scheduler: Scheduler; + + constructor(scheduler: Scheduler) { + this.scheduler = scheduler; + } + + async removeBlacklist( + type: 'server', + hubId: string, + serverId: string, + ): Promise; + async removeBlacklist( + type: 'user', + hubId: string, + userId: string, + ): Promise; + async removeBlacklist( + type: 'user' | 'server', + hubId: string, + userOrServerId: string, + ) { + // FIXME find a better way to pass scheduler + this.scheduler.stopTask(`blacklist_${type}-${userOrServerId}`); + + if (type === 'user') { + return await db.blacklistedUsers.update({ + where: { + userId: userOrServerId, + hubs: { some: { hubId } }, + }, + data: { + hubs: { deleteMany: { where: { hubId } } }, + }, + }); + } + else { + return db.blacklistedServers.update({ + where: { + serverId: userOrServerId, + hubs: { some: { hubId } }, + }, + data: { + hubs: { deleteMany: { where: { hubId } } }, + }, + }); + } + } + + async scheduleRemoval( + type: 'server' | 'user', + userOrServerId: string, + hubId: string, + expires: Date | number, + ) { + let name: string; + let execute; + + if (type === 'server') { + name = `unblacklistServer-${userOrServerId}`; + execute = () => this.removeBlacklist('server', hubId, userOrServerId); + } + else { + name = `unblacklistUser-${userOrServerId}`; + execute = () => this.removeBlacklist('user', hubId, userOrServerId); + } + + this.scheduler.addTask(name, expires, execute); + } + + /* static methods (can be access without initializing class) */ + static async notifyBlacklist( + userOrChannel: User | TextBasedChannel, + hubId: string, + expires?: Date, + reason: string = 'No reason provided.', + ) { + const hub = await db.hubs.findUnique({ where: { id: hubId } }); + const expireString = expires ? `` : 'Never'; + let embed: EmbedBuilder; + + if (userOrChannel instanceof User) { + embed = new EmbedBuilder() + .setTitle(emojis.blobFastBan + ' Blacklist Notification') + .setDescription(`You have been blacklisted from talking in hub **${hub?.name}**.`) + .setColor(colors.interchatBlue) + .setFields( + { name: 'Reason', value: reason, inline: true }, + { name: 'Expires', value: expireString, inline: true }, + ); + } + else { + embed = new EmbedBuilder() + .setTitle(emojis.blobFastBan + ' Blacklist Notification') + .setDescription(`This server has been blacklisted from talking in hub **${hub?.name}**.`) + .setColor(colors.interchatBlue) + .setFields( + { name: 'Reason', value: reason, inline: true }, + { name: 'Expires', value: expireString, inline: true }, + ); + } + + try { + await userOrChannel.send({ embeds: [embed] }).catch(() => null); + } + catch (e) { + Logger.error(e); + captureException(e); + } + } + + + static async fetchUserBlacklist(hubId: string, userId: string) { + const userBlacklisted = await db.blacklistedUsers.findFirst({ + where: { userId, hubs: { some: { hubId } } }, + }); + return userBlacklisted; + } + + + static async fetchServerBlacklist(hubId: string, serverId: string) { + const userBlacklisted = await db.blacklistedServers.findFirst({ + where: { serverId, hubs: { some: { hubId } } }, + }); + return userBlacklisted; + } + + + static async addUserBlacklist( + hubId: string, + userId: string, + reason: string, + expires?: Date | number, + ) { + const client = SuperClient.getInstance(); + const user = await client.users.fetch(userId); + if (typeof expires === 'number') expires = new Date(Date.now() + expires); + + const dbUser = await db.blacklistedUsers.findFirst({ where: { userId: user.id } }); + + const hubs = dbUser?.hubs.filter((i) => i.hubId !== hubId) || []; + hubs?.push({ expires: expires || null, reason, hubId }); + + const updatedUser = await db.blacklistedUsers.upsert({ + where: { + userId: user.id, + }, + update: { + username: user.username, + hubs: { set: hubs }, + }, + create: { + userId: user.id, + username: user.username, + hubs, + }, + }); + + return updatedUser; + } + + + static async addServerBlacklist( + client: SuperClient, + serverId: string, + hubId: string, + reason: string, + expires?: Date, + ) { + const guild = await client.fetchGuild(serverId); + if (!guild) return; + + const dbGuild = await db.blacklistedServers.upsert({ + where: { + serverId: guild.id, + }, + update: { + serverName: guild.name, + hubs: { push: { hubId, expires, reason } }, + }, + create: { + serverId: guild.id, + serverName: guild.name, + hubs: [{ hubId, expires, reason }], + }, + }); + + return dbGuild; + } +} + diff --git a/src/structures/CommandHandler.ts b/src/structures/CommandHandler.ts new file mode 100644 index 00000000..d3f1606c --- /dev/null +++ b/src/structures/CommandHandler.ts @@ -0,0 +1,63 @@ +import { Interaction } from 'discord.js'; +import fs from 'fs'; +import path from 'path'; +import Factory from '../Factory.js'; +import { CustomID } from './CustomID.js'; + +const __filename = new URL(import.meta.url).pathname; +const __dirname = path.dirname(__filename); + +export default class CommandHandler extends Factory { + public get commandsMap() { + return this.client.commands; + } + + /** `InteractionCreate` event handler */ + handleInteraction(interaction: Interaction) { + if (interaction.isAutocomplete()) { + const command = this.client.commands.get(interaction.commandName); + if (command?.autocomplete) command.autocomplete(interaction); + } + + else if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { + this.client.commands.get(interaction.commandName)?.execute(interaction); + } + + else { + const customId = CustomID.toJSON(interaction.customId); + const handler = this.client.components.find((_, key) => key.startsWith(customId.identifier)); + if (!handler) { + interaction.reply({ content: 'This button is no longer usable.', ephemeral: true }); + return; + } + handler(interaction); + } + } + + /** + * Loads all commands from the Commands directory + * Commands are automatically added to the `clientCommands` map + */ + static async loadCommandFiles( + commandDir = path.join(__dirname, '..', 'commands'), + ): Promise { + const files = fs.readdirSync(commandDir); + + for (const file of files) { + const filePath = path.join(commandDir, file); + const stats = fs.statSync(filePath); + + if (stats.isDirectory()) { + // If the item is a directory, recursively read its files + await this.loadCommandFiles(filePath); + } + + // If the item is a .js file, read its contents + else if (file.endsWith('.js') && file !== 'Command.js') { + // importing it will automatically add the command to the clientCommands map + const command = await import(filePath); + new command.default().loadCommand(); + } + } + } +} diff --git a/src/structures/CustomID.ts b/src/structures/CustomID.ts new file mode 100644 index 00000000..4e31febc --- /dev/null +++ b/src/structures/CustomID.ts @@ -0,0 +1,51 @@ +export class CustomID { + private customId: string; + private data: string[]; + + constructor(identifier?: string, data: string[] = []) { + this.customId = identifier ?? ''; + this.data = data; + } + + setIdentifier(prefix: string, postfix?: string): CustomID { + this.customId = prefix + (postfix ? `:${postfix}` : ''); + return this; + } + + addData(value: string): CustomID { + if (!value) return this; + + this.customId += `&${value}`; + return this; + } + + static toJSON(customId: string) { + const parsedId = { + identifier: '', + postfix: '', + data: [] as string[], + }; + + for (const [index, part] of customId.split('&').entries()) { + if (index === 0) { + const [identifier, postfix] = part.split(':'); + parsedId.identifier = identifier; + parsedId.postfix = postfix ?? ''; + } + else { + parsedId.data.push(part); + } + } + + return parsedId; + } + + toString() { + let str = `${this.customId}`; + if (this.data.length > 0) this.data.forEach((element) => (str += `&${element}`)); + + if (str.length > 100) throw new TypeError('Custom ID cannot be longer than 100 characters.'); + + return str; + } +} diff --git a/src/structures/NSFWDetection.ts b/src/structures/NSFWDetection.ts new file mode 100644 index 00000000..31c39a62 --- /dev/null +++ b/src/structures/NSFWDetection.ts @@ -0,0 +1,29 @@ +import { REGEX } from '../utils/Constants.js'; + +export declare const NSFW_CLASSES: { + [classId: number]: 'Drawing' | 'Hentai' | 'Neutral' | 'Porn' | 'Sexy'; +}; + +export declare type predictionType = { + className: typeof NSFW_CLASSES[keyof typeof NSFW_CLASSES]; + probability: number; +}; + +export default class NSFWDetector { + public async analyzeImage(url: string): Promise { + if (!REGEX.STATIC_IMAGE_URL.test(url)) return null; + const res = await fetch(`http://localhost:3000/nsfw?url=${url}`); + + return res.status === 200 ? await res.json() : null; + } + + public isUnsafeContent(predictions: predictionType[]): boolean { + const safeCategories = ['Neutral', 'Drawing']; + + return predictions.some( + (prediction) => + !safeCategories.includes(prediction.className) && + prediction.probability > 0.6, + ); + } +} \ No newline at end of file diff --git a/src/structures/NetworkManager.ts b/src/structures/NetworkManager.ts new file mode 100644 index 00000000..2c7fa588 --- /dev/null +++ b/src/structures/NetworkManager.ts @@ -0,0 +1,358 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + HexColorString, + Message, + WebhookMessageCreateOptions, +} from 'discord.js'; +import Factory from '../Factory.js'; +import db from '../utils/Db.js'; +import { Prisma, connectedList, hubs } from '@prisma/client'; +import { REGEX, emojis } from '../utils/Constants.js'; +import { censor } from '../utils/Profanity.js'; +import { stripIndents } from 'common-tags'; +import { HubSettingsBitField } from '../utils/BitFields.js'; + +export interface NetworkMessage extends Message { + censoredContent: string; +} + +export interface NetworkWebhookSendResult { + messageOrError: Message | string; + webhookURL: string; +} + +export interface Networks extends connectedList { + hub: hubs | null; +} + +export default class NetworkManager extends Factory { + public async handleNetworkMessage(message: NetworkMessage, network: Networks) { + const settings = new HubSettingsBitField(network.hub?.settings); + const checksPassed = await this.runChecks(message, settings); + if (!checksPassed) return; + + message.censoredContent = censor(message.content); + + const attachment = message.attachments.first(); + const attachmentURL = attachment + ? `attachment://${attachment.name}` + : await this.getAttachmentURL(message); + + if (attachmentURL) { + const nsfwDetector = this.client.getNSFWDetector(); + const predictions = await nsfwDetector.analyzeImage( + attachment ? attachment.url : attachmentURL, + ); + + if (predictions && nsfwDetector.isUnsafeContent(predictions)) { + message.react(emojis.loading); + + const nsfwEmbed = new EmbedBuilder() + .setTitle('NSFW Image Detected') + .setDescription(stripIndents` + I have identified this image as NSFW (Not Safe For Work). Sharing NSFW content is against our network guidelines. Refrain from posting such content here. + + **NSFW Prediction:** ${predictions[0].className} - ${Math.round(predictions[0].probability * 100)}%`, + ) + .setFooter({ + text: 'Please be aware that AI predictions can be inaccurate at times, and we cannot guarantee perfect accuracy in all cases. 😔', + iconURL: 'https://i.imgur.com/625Zy9W.png', + }) + .setColor('Red'); + + return await message.reply({ embeds: [nsfwEmbed] }); + } + } + + // fetch the referred message (message being replied to) from discord + const referredMessage = message.reference ? await message.fetchReference() : undefined; + // check if it was sent in the network + const referenceInDb = referredMessage + ? await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: referredMessage?.id } } }, + }) + : undefined; + const referredContent = referenceInDb ? await this.getReferredContent(message) : undefined; + + + // embeds for the normal mode + const { embed, censoredEmbed } = this.buildNetworkEmbed(message, { + attachmentURL, + referredContent, + }); + + // loop through all connections and send the message + const allConnections = await this.fetchHubNetworks({ hubId: network.hubId }); + const sendResult = allConnections.map(async (connection) => { + try { // parse the webhook url and get the webhook id and token + const webhookURL = connection.webhookURL.split('/'); + // fetch the webhook from discord + const webhook = await this.client.fetchWebhook( + webhookURL[webhookURL.length - 2], + webhookURL[webhookURL.length - 1], + ); + + const reply = referenceInDb?.channelAndMessageIds.find( + (msg) => msg.channelId === connection.channelId, + ); + // create a jump button to reply button + const jumpButton = + reply && referredMessage?.author + ? new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.reply) + .setURL( + `https://discord.com/channels/${connection.serverId}/${reply.channelId}/${reply.messageId}`, + ) + .setLabel( + referredMessage.author.username.length >= 80 + ? '@' + referredMessage.author.username.slice(0, 76) + '...' + : '@' + referredMessage.author.username, + ), + ) + : null; + + // embed format + let messageFormat: WebhookMessageCreateOptions = { + components: jumpButton ? [jumpButton] : undefined, + embeds: [connection.profFilter ? censoredEmbed : embed], + files: attachment ? [attachment] : undefined, + username: `${network.hub?.name}`, + avatarURL: network.hub?.iconUrl, + threadId: connection.parentId ? connection.channelId : undefined, + allowedMentions: { parse: [] }, + }; + + if (connection.compact) { + const replyContent = + connection.profFilter && referredContent ? censor(referredContent) : referredContent; + + // preview embed for the message being replied to + const replyEmbed = replyContent + ? new EmbedBuilder({ + description: + replyContent.length > 30 ? replyContent?.slice(0, 30) + '...' : replyContent, + author: { + name: `${referredMessage?.author.username.slice(0, 30)}`, + icon_url: referredMessage?.author.displayAvatarURL(), + }, + }).setColor('Random') + : undefined; + + // compact format (no embeds, only content) + messageFormat = { + embeds: replyEmbed ? [replyEmbed] : undefined, + components: jumpButton ? [jumpButton] : undefined, + content: connection.profFilter ? message.censoredContent : message.content, + files: attachment ? [attachment] : undefined, + username: message.author.username, + avatarURL: message.author.displayAvatarURL(), + threadId: connection.parentId ? connection.channelId : undefined, + allowedMentions: { parse: [] }, + }; + } + + // send the message + const messageOrError = await webhook.send(messageFormat); + // return the message and webhook URL to store the message in the db + return { messageOrError, webhookURL: connection.webhookURL } as NetworkWebhookSendResult; + } + catch (e) { + // return the error and webhook URL to store the message in the db + return { messageOrError: e.message, webhookURL: connection.webhookURL } as NetworkWebhookSendResult; + } + }); + + message.delete().catch(() => null); + + // store the message in the db + await this.storeMessageData(message, await Promise.all(sendResult), network.hubId); + } + + public async runChecks(message: Message, settings: HubSettingsBitField): Promise { + const isUserBlacklisted = await db.blacklistedUsers.findFirst({ + where: { userId: message.author.id }, + }); + + if (isUserBlacklisted) return false; + + if (message.content.length > 1000) { + message.reply('Your message is too long! Please keep it under 1000 characters.'); + return false; + } + + if ( + settings.has('BlockInvites') && + message.content.includes('discord.gg') || + message.content.includes('discord.com/invite') || + message.content.includes('dsc.gg') + ) { + message.reply( + 'Do not advertise or promote servers in the network. Set an invite in `/network manage` instead!', + ); + return false; + } + + if (message.stickers.size > 0 && !message.content) { + message.reply( + 'Sending stickers in the network is not possible due to discord\'s limitations.', + ); + return false; + } + + // TODO allow multiple attachments when embeds can have multiple images + const attachment = message.attachments.first(); + const allowedTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/jpg']; + + if (attachment?.contentType) { + if (allowedTypes.includes(attachment.contentType) === false) { + message.reply('Only images and gifs are allowed to be sent within the network.'); + return false; + } + + if (attachment.size > 1024 * 1024 * 8) { + message.reply('Please keep your attachments under 8MB.'); + return false; + } + } + + return true; + } + + public async getReferredContent(referredMessage: Message) { + let referredContent = referredMessage.content || referredMessage.embeds[0]?.description; + + if (!referredContent) { + referredContent = '*Original message contains attachment <:attachment:1102464803647275028>*'; + } + else if (referredContent.length > 1000) { + referredContent = referredContent.slice(0, 1000) + '...'; + } + + return referredContent; + } + + public async getAttachmentURL(message: Message) { + // Tenor Gifs / Image URLs + const URLMatch = message.content.match(REGEX.STATIC_IMAGE_URL); + + if (URLMatch) return URLMatch[0]; + + const tenorRegex = /https:\/\/tenor\.com\/view\/.*-(\d+)/; + const gifMatch = message.content.match(tenorRegex); + + if (gifMatch) { + if (!process.env.TENOR_KEY) throw new TypeError('Tenor API key not found in .env file.'); + + const n = gifMatch[0].split('-'); + const id = n.at(-1); + const api = `https://g.tenor.com/v1/gifs?ids=${id}&key=${process.env.TENOR_KEY}`; + const gifJSON = await (await fetch(api)).json(); + + return gifJSON.results[0].media[0].gif.url as string; + } + return null; + } + + public buildNetworkEmbed( + message: NetworkMessage, + opts?: { attachmentURL?: string | null; embedCol?: HexColorString; referredContent?: string }, + ): { embed: EmbedBuilder; censoredEmbed: EmbedBuilder } { + const embed = new EmbedBuilder({ + description: message.content, + image: opts?.attachmentURL ? { url: opts?.attachmentURL } : undefined, + author: { + name: message.author.username, + icon_url: message.author.displayAvatarURL(), + }, + footer: { + text: `From: ${message.guild?.name}`, + icon_url: message.guild?.iconURL() ?? undefined, + }, + fields: opts?.referredContent + ? [{ name: 'Replying To:', value: opts.referredContent ?? 'Unknown.' }] + : undefined, + }).setColor(opts?.embedCol ?? 'Random'); + + const censoredEmbed = EmbedBuilder.from({ + ...embed.toJSON(), + description: message.censoredContent, + fields: opts?.referredContent + ? [{ name: 'Replying To:', value: censor(opts.referredContent) ?? 'Unknown.' }] + : undefined, + }); + + return { embed, censoredEmbed }; + } + + // TODO: Error handlers for these + public async fetchHubNetworks(where: { hubId?: string; hubName?: string }) { + return await db.connectedList.findMany({ where }); + } + + public async fetchConnection(where: Prisma.connectedListWhereUniqueInput) { + return await db.connectedList.findUnique({ where }); + } + + async updateConnection(where: Prisma.connectedListWhereUniqueInput, data: Prisma.connectedListUpdateInput) { + return await db.connectedList.update({ where, data }); + } + + /** + * Stores message in the db after it has been sent to the network + * And disconnects the network if the webhook is invalid + * */ + protected async storeMessageData( + message: Message, + channelAndMessageIds: NetworkWebhookSendResult[], + hubId: string, + ): Promise { + const messageDataObj: { channelId: string; messageId: string }[] = []; + const invalidWebhookURLs: string[] = []; + + // loop through all results and extract message data and invalid webhook urls + channelAndMessageIds.forEach((result) => { + if (typeof result.messageOrError === 'string') { + if ( + result.messageOrError.includes('Invalid Webhook Token') || + result.messageOrError.includes('Unknown Webhook') + ) { + invalidWebhookURLs.push(result.webhookURL); + } + } + else { + messageDataObj.push({ + channelId: result.messageOrError.channelId, + messageId: result.messageOrError.id, + }); + } + }); + + if (message.guild && hubId) { + // store message data in db + await db.messageData.create({ + data: { + hub: { connect: { id: hubId } }, + channelAndMessageIds: messageDataObj, + timestamp: message.createdAt, + authorId: message.author.id, + serverId: message.guild.id, + reference: message.reference, + reactions: {}, + }, + }); + } + + // disconnect network if, webhook does not exist/bot cannot access webhook + if (invalidWebhookURLs.length > 0) { + await db.connectedList.updateMany({ + where: { webhookURL: { in: invalidWebhookURLs } }, + data: { connected: false }, + }); + } + } +} diff --git a/src/structures/Scheduler.ts b/src/structures/Scheduler.ts new file mode 100644 index 00000000..7a09850a --- /dev/null +++ b/src/structures/Scheduler.ts @@ -0,0 +1,38 @@ +export class Scheduler { + private tasks: Map void; interval: number; intervalId: NodeJS.Timeout }>; + + constructor() { + this.tasks = new Map(); + } + + addTask(name: string, interval: number | Date, task: () => void): void { + if (this.tasks.has(name)) { + throw new Error(`Task with name ${name} already exists.`); + } + + // if interval is instance of Date, convert it to milliseconds + interval = interval instanceof Date ? interval.getTime() - Date.now() : interval; + + const intervalId = setInterval(task, interval); + this.tasks.set(name, { task, interval, intervalId }); + } + + stopTask(taskName: string): boolean | undefined { + const taskInfo = this.tasks.get(taskName); + if (taskInfo) { + clearInterval(taskInfo.intervalId); + return this.tasks.delete(taskName); + } + return; + } + stopAllTasks(): void { + this.tasks.forEach((taskInfo, taskName) => { + clearInterval(taskInfo.intervalId); + this.tasks.delete(taskName); + }); + } + + get taskNames(): string[] { + return Array.from(this.tasks.keys()); + } +} diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts new file mode 100644 index 00000000..0dca41bd --- /dev/null +++ b/src/typings/index.d.ts @@ -0,0 +1,27 @@ +import { ClusterClient } from 'discord-hybrid-sharding'; +import { Collection, Snowflake } from 'discord.js'; +import { Logger } from 'winston'; +import CommandHandler from '../structures/CommandHandler'; +import NSFWClient from '../structures/NSFWDetection.ts'; +import NetworkManager from '../structures/NetworkManager.ts'; +import { Scheduler } from '../structures/Scheduler.ts'; + +declare module 'discord.js' { + export interface Client { + readonly logger: Logger; + readonly version: string; + readonly development: boolean; + readonly description: string; + readonly commandCooldowns: Collection; + readonly reactionCooldowns: Collection; + readonly cluster: ClusterClient; + + fetchGuild(guildId: Snowflake): Promise; + getScheduler(): Scheduler; + getCommandManager(): CommandHandler; + getCommandManager(): CommandHandler; + getNetworkManager(): NetworkManager; + getBlacklistManager(): BlacklistManager; + getNSFWDetector(): NSFWClient; + } +} diff --git a/src/updater/ReactionUpdater.ts b/src/updater/ReactionUpdater.ts new file mode 100644 index 00000000..0eaefa33 --- /dev/null +++ b/src/updater/ReactionUpdater.ts @@ -0,0 +1,151 @@ +import db from '../utils/Db.js'; +import Factory from '../Factory.js'; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, + MessageReaction, + PartialMessageReaction, + PartialUser, + User, + WebhookClient, +} from 'discord.js'; +import { + connectedList, + MessageDataChannelAndMessageIds, +} from '@prisma/client'; +import { sortReactions } from '../utils/Utils.js'; +import { BlacklistManager } from '../structures/BlacklistManager.js'; +import { HubSettingsBitField } from '../utils/BitFields.js'; + +export default class ReactionUpdater extends Factory { + public async listen( + reaction: MessageReaction | PartialMessageReaction, + user: User | PartialUser, + ): Promise { + if (user.bot || user.system) return; + + const messageInDb = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: reaction.message.id } } }, + include: { hub: { select: { settings: true } } }, + }); + + if ( + !messageInDb?.hub || + !messageInDb.hubId || + !new HubSettingsBitField(messageInDb.hub.settings).has('Reactions') || + !reaction.message.inGuild() + ) { + return; + } + + const userBlacklisted = await BlacklistManager.fetchUserBlacklist(messageInDb.hubId, user.id); + const serverBlacklisted = await BlacklistManager.fetchUserBlacklist( + messageInDb.hubId, + reaction.message.guild.id, + ); + if (userBlacklisted || serverBlacklisted) return; + + const connections = await db.connectedList.findMany({ + where: { + channelId: { in: messageInDb.channelAndMessageIds.map((c) => c.channelId) }, + connected: true, + }, + }); + + const reactedEmoji = reaction.emoji.toString(); + const reactions = messageInDb.reactions?.valueOf() as { [key: string]: string[] }; // eg. { '👍': 1, '👎': 2 } + + if ( + (!reactions[reactedEmoji] && Object.keys(reactions).length >= 10) || + reactions[reactedEmoji]?.includes(user.id) + ) { + return; + } + + reactions[reactedEmoji] + ? reactions[reactedEmoji].push(user.id) + : (reactions[reactedEmoji] = [user.id]); + + await db.messageData.update({ + where: { id: messageInDb.id }, + data: { reactions: reactions }, + }); + + reaction.users.remove(user.id).catch(() => null); + this.updateReactions(connections, messageInDb.channelAndMessageIds, reactions); + } + + updateReactions( + connections: connectedList[], + channelAndMessageIds: MessageDataChannelAndMessageIds[], + reactions: { [key: string]: string[] }, + ): void { + // reactions data example: { '👍': ['userId1', 'userId2'], '👎': ['userId1', 'userId2', 'userId3'] } + // sortedReactions[0] = array of [emoji, users[]] + // sortedReactions[x] = emojiIds + // sortedReactions[x][y] = arr of users + const sortedReactions = sortReactions(reactions); + const reactionCount = sortedReactions[0][1].length; + const mostReaction = sortedReactions[0][0]; + + const reactionBtn = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`reaction_${mostReaction}`) + .setEmoji(mostReaction) + .setStyle(ButtonStyle.Secondary) + .setLabel(`${reactionCount}`), + ); + + if (sortedReactions.length > 1) { + const allReactionCount = sortedReactions.filter( + (e) => e[0] !== mostReaction && e[1].length > 0, + ); + if (allReactionCount.length > 0) { + reactionBtn.addComponents( + new ButtonBuilder() + .setCustomId('view_all_reactions') + .setStyle(ButtonStyle.Secondary) + .setLabel(`+ ${allReactionCount.length}`), + ); + } + } + + connections.forEach(async (connection) => { + const dbMsg = channelAndMessageIds.find((e) => e.channelId === connection.channelId); + if (!dbMsg) return; + + const webhook = new WebhookClient({ url: connection.webhookURL }); + const message = await webhook + .fetchMessage(dbMsg.messageId, { + threadId: connection.parentId ? connection.channelId : undefined, + }) + .catch(() => null); + + const components = message?.components?.filter((row) => { + // filter all buttons that are not reaction buttons + row.components = row.components.filter((component) => { + return component.type === ComponentType.Button && + component.style === ButtonStyle.Secondary + ? !component.custom_id.startsWith('reaction_') && + component.custom_id !== 'view_all_reactions' + : true; + }); + + // if the filtered row has components, that means it has components other than reaction buttons + // so we return true to keep the row + return row.components.length > 0; + }); + + if (reactionCount > 0) components?.push(reactionBtn.toJSON()); + + webhook + .editMessage(dbMsg.messageId, { + components, + threadId: connection.parentId ? connection.channelId : undefined, + }) + .catch(() => null); + }); + } +} diff --git a/src/updater/StatsUpdater.ts b/src/updater/StatsUpdater.ts new file mode 100644 index 00000000..52f74340 --- /dev/null +++ b/src/updater/StatsUpdater.ts @@ -0,0 +1,15 @@ +import { URLs } from '../utils/Constants.js'; +export const updateTopGGStats = async (totalGuilds: number, totalShards = 1) => { + if (!process.env.TOPGG_API_KEY) throw new TypeError('Missing TOPGG_TOKEN environment variable'); + + await fetch(`${URLs.TOPGG_API}/stats`, { + method: 'POST', + headers: { + Authorization: process.env.TOPGG_API_KEY, + }, + body: JSON.stringify({ + server_count: totalGuilds, + shard_count: totalShards, + }), + }); +}; diff --git a/src/Utils/hubSettingsBitfield.ts b/src/utils/BitFields.ts similarity index 95% rename from src/Utils/hubSettingsBitfield.ts rename to src/utils/BitFields.ts index dc971b1e..02f8df99 100644 --- a/src/Utils/hubSettingsBitfield.ts +++ b/src/utils/BitFields.ts @@ -6,12 +6,14 @@ export const HubSettingsBits = { SpamFilter: 1 << 2, BlockInvites: 1 << 3, UseNicknames: 1 << 4, -} as const; + BlockNSFW: 1 << 5, +}; export type HubSettingsString = keyof typeof HubSettingsBits; export class HubSettingsBitField extends BitField { public static Flags = HubSettingsBits; + /** toggle a setting */ public toggle(...setting: HubSettingsString[]) { return this.has(setting) ? this.remove(setting) : this.add(setting); diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts new file mode 100644 index 00000000..c1a8c30e --- /dev/null +++ b/src/utils/Constants.ts @@ -0,0 +1,102 @@ +import { stripIndents } from 'common-tags'; +import { Colors, EmbedBuilder, HexColorString } from 'discord.js'; +import { normal, badge, mascot } from './JSON/emojis.json'; +import { createRequire } from 'module'; +import 'dotenv/config'; + +const require = createRequire(import.meta.url); +const emotes = require('./JSON/emojis.json'); + +export const isDevBuild = process.env.NODE_ENV === 'development'; + +export const CLIENT_ID = isDevBuild ? '798748015435055134' : '769921109209907241'; +export const SUPPORT_SERVER_ID = '770256165300338709'; + +export const emojis: typeof normal = emotes.normal; +export const mascotEmojis: typeof mascot = emotes.mascot; +export const badgeEmojis: typeof badge = emotes.badge; + +// Regexp +export const REGEX = { + IMAGE_URL: /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.gif|\.png)/, + /** no animated images */ + STATIC_IMAGE_URL: /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.png)/, +}; + +export const StaffIds = ['597265261665714186', '442653948630007808', '689082827979227160'] as const; +export const DeveloperIds = [ + '828492978716409856', + '701727675311587358', + '456961943505338369', +] as const; + + +export const URLs = { + TOPGG_API: 'https://top.gg/api/bots/769921109209907241', + VOTE: 'https://top.gg/bot/769921109209907241/vote', + DOCS: 'https://discord-interchat.github.io/docs', +} as const; + +export const channel = { + bugs: '1035135196053393418', + networklogs: '1156144879869632553', + modlogs: '1042265633896796231', + reports: '1158773603551162398', + goal: '906460473065615403', + suggestions: '1021256657528954900', +} as const; + +export const colors = { + all: [ + 'Default', + 'White', + 'Aqua', + 'Green', + 'Blue', + 'Yellow', + 'Purple', + 'LuminousVividPink', + 'Fuchsia', + 'Gold', + 'Orange', + 'Red', + 'Grey', + 'DarkNavy', + 'DarkAqua', + 'DarkGreen', + 'DarkBlue', + 'DarkPurple', + 'DarkVividPink', + 'DarkGold', + 'DarkOrange', + 'DarkRed', + 'DarkGrey', + 'DarkerGrey', + 'LightGrey', + 'DarkNavy', + 'Blurple', + 'Greyple', + 'DarkButNotBlack', + 'NotQuiteBlack', + 'Random', + ] as (keyof typeof Colors)[], + interchatBlue: '#5CB5F9' as HexColorString, + invisible: '#2F3136' as HexColorString, + christmas: ['#00B32C', '#D6001C', '#FFFFFF'] as HexColorString[], +} as const; + +export const rulesEmbed = new EmbedBuilder() + .setColor(colors.interchatBlue) + .setImage('https://i.imgur.com/D2pYagc.png').setDescription(stripIndents` + ### 📜 InterChat Network Rules + + 1. **Use Common Sense:** Be considerate of others and their views. No slurs, derogatory language or any actions that can disrupt the chat's comfort. + 2. **No Spamming or Flooding:** Avoid repeated, nonsensical, or overly lengthy messages. + 3. **Keep Private Matters Private:** Avoid sharing personal information across the network. + 4. **No Harassment:** Trolling, insults, or harassment of any kind are not tolerated. + 5. **No NSFW/NSFL Content:** Posting explicit NSFW/NSFL content will result in immediate blacklist. + 6. **Respect Sensitive Topics:** Do not trivialize self-harm, suicide, violence, or other offensive topics. + 7. **Report Concerns:** If you observe a violation of these rules, report it to the appropriate hub moderator or InterChat staff for further action. + + Any questions? Join our [support server](https://discord.gg/6bhXQynAPs). +`); diff --git a/src/utils/Db.ts b/src/utils/Db.ts new file mode 100644 index 00000000..37c0955f --- /dev/null +++ b/src/utils/Db.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from '@prisma/client'; + +const db = new PrismaClient(); + +export default db; \ No newline at end of file diff --git a/src/utils/JSON/emojis.json b/src/utils/JSON/emojis.json new file mode 100644 index 00000000..4b8b029c --- /dev/null +++ b/src/utils/JSON/emojis.json @@ -0,0 +1,91 @@ +{ + "normal": { + "yes": "<:yes:977600103206498354>", + "neutral": "<:neutral:977600034055016499>", + "no": "<:no:977600033740431410>", + "discordStaff": "<:badge_InterChatStaff:1102229356379648011>", + "invite": "<:add:1032318201134067722>", + "clipart": "<:chat_clipart:772393314413707274>", + "checkGreen": "", + "tada": "", + "shinyStaff": "", + "enabled": " <:enabled:991180941311619205>", + "disabled": "<:disabled:991180939122180146>", + "ID": "<:blurple_id:1032318192741261393>", + "loading": "", + "bruhcat": "<:bruhcat:976368249858056223>", + "tick": "<:tick:1032318190144995399>", + "force_enabled": "<:devportal_enabled:994444521888821338>", + "chatbot_circle": "<:chatbot_circle:1077200892157243503>", + "dotYellow": "<:yellowdot:986617776196190248>", + "dotRed": "<:reddot:986617774006738944>", + "dotBlue": "<:dotBlue:1086649062775865364>", + "webhook": "<:webhook:1065979539320217621>", + "online_anim": "", + "idle_anim": "", + "dnd_anim": "", + "offline_anim": "", + "onlineMobile": "<:onlinemobile:715050614429712384>", + "dndMobile": "<:dndmobile:715050614047899741>", + "idleMobile": "<:idlemobile:715050614278717500>", + "description": "<:description:1035140306414342164>", + "slash": "<:slash:1049191025903685733>", + "reply": "<:reply:1064781138477985792>", + "blobFastBan": "", + "back": "<:back:1082924285674401852>", + "forward": "<:forward:1082924287922536508>", + "dividerEnd": "<:divider_end:1077200586803527690>", + "contextMenu": "<:ContextMenuCommand:1086624007949930597>", + "slashCommand": "<:slashCommand:1086631449337266276>", + "blueLine": "<:blueLine:1086634970539360296>", + "attachment": "<:attachment:1102464803647275028>", + "interchatLogoOld": "<:interchat:1155133495270703135>", + "interchatCircle": "<:InterChat:1156787716130869269>", + "bot": "<:icons_robot:1032485570162720888>", + "comment": "<:comment:983962459406733312>", + "colorstaff": "<:icons_colorstaff:978937000021332009>", + "join": "<:join:990488359279419443>", + "leave": "<:leave:990488356553097236>", + "mention": "<:mention:991909883903475822>", + "id": "<:id:991912629985628240>", + "owner": "<:owner:992026201478680596>", + "next": "<:next:983284968992169984>", + "previous": "<:back:983284970804113478>", + "delete": "<:delete:983284966983082014>", + "store": "<:icons_store:978937006094712842>", + "exclamation": "<:icons_exclamation:978937002760237056>", + "info": "<:info:1032318204023947414>", + "settings": "<:settings:1032320777963450388>", + "search": "<:search:1032320775635619920>", + "activities": "<:activity:1032320780807192626>", + "wand": "<:wand:1032320783386685440>", + "connect": "<:connect:1065977170788352110>", + "disconnect": "<:disconnect:1065977693075673160>", + "members": "<:members:1032318206964146246>", + "staff": "<:staff:983962462380511282>", + "botdev": "<:botdev2:983962441329307659>", + "link": "<:link:1032318209879179295>", + "timeout": "<:timeout:1088474245908152461>" + }, + + "mascot": { + "cry": "<:chipi_cry:1065863977957072977>", + "flushed": "<:chipi_flushed:1065863982679871580>", + "forwn": "<:chipi_frown:1065864007187189760>", + "huh": "<:chipi_huh:1065863996244246559>", + "neutral": "<:chipi_neutral:1065864014942441493>", + "rage": "<:chipi_rage:1065864002405683210>", + "smile": "<:chipi_smile:1065867304480559144>", + "ugh": "<:chipi_ugh:1065863991815049316>" + }, + + "badge": { + "Developer": "<:badge_developer:994549313013284904>", + "Staff": "<:badge_InterChatStaff:1102229356379648011>", + "Premium": "<:badge_BugHunter3:994262351266185317>", + "Voter": "<:topgg_voter:1065977698058506290>", + "Moderator": "<:badge_CertifiedModerator:994444514989182986>", + "BugHunter": "<:badge_BugHunter31:994445137818169416>", + "Christmas2022": "<:Christmas2022:1056214953666814002>" + } +} diff --git a/src/utils/JSON/profanity.json b/src/utils/JSON/profanity.json new file mode 100644 index 00000000..120a14ec --- /dev/null +++ b/src/utils/JSON/profanity.json @@ -0,0 +1,383 @@ +{ + "slurs": ["nigga", "nigg", "nigger", "n1gger", "n1gg3r", "niger", "kys"], + + "profanity": [ + "4r5e", + "5h1t", + "5hit", + "a55", + "anal", + "ar5e", + "arrse", + "arse", + "ass", + "ass-fucker", + "asses", + "assfucker", + "assfukka", + "asshole", + "assholes", + "asswhole", + "a_s_s", + "b!tch", + "b00bs", + "b17ch", + "b1tch", + "ballbag", + "ballsack", + "beastial", + "beastiality", + "bellend", + "bestial", + "bestiality", + "bi+ch", + "biatch", + "bitch", + "bitcher", + "bitchers", + "bitches", + "bitchin", + "bitching", + "bloody", + "blow job", + "blowjob", + "blowjobs", + "boiolas", + "bollock", + "bollok", + "boner", + "boob", + "boobs", + "booobs", + "boooobs", + "booooobs", + "booooooobs", + "buceta", + "bugger", + "bunny fucker", + "c0ck", + "c0cksucker", + "carpet muncher", + "cawk", + "chink", + "cipa", + "cl1t", + "clit", + "clitoris", + "clits", + "cnut", + "cock", + "cock-sucker", + "cockface", + "cockhead", + "cockmunch", + "cockmuncher", + "cocks", + "cocksuck", + "cocksucked", + "cocksucker", + "cocksucking", + "cocksucks", + "cocksuka", + "cocksukka", + "cok", + "cokmuncher", + "coksucka", + "coon", + "cox", + "cum", + "cummer", + "cumming", + "cums", + "cumshot", + "cunilingus", + "cunillingus", + "cunnilingus", + "cunt", + "cuntlick", + "cuntlicker", + "cuntlicking", + "cunts", + "cyalis", + "cyberfuc", + "cyberfuck", + "cyberfucked", + "cyberfucker", + "cyberfuckers", + "cyberfucking", + "d1ck", + "dick", + "dickhead", + "dildo", + "dildos", + "dirsa", + "dlck", + "dog-fucker", + "donkeyribber", + "doosh", + "duche", + "dyke", + "ejaculate", + "ejaculated", + "ejaculates", + "ejaculating", + "ejaculatings", + "ejaculation", + "ejakulate", + "f u c k", + "f u c k e r", + "f4nny", + "fag", + "fagging", + "faggitt", + "faggot", + "faggs", + "fagot", + "fagots", + "fags", + "fanny", + "fannyflaps", + "fannyfucker", + "fanyy", + "fcuk", + "fcuker", + "fcuking", + "feck", + "fecker", + "felching", + "fellate", + "fellatio", + "fingerfuck", + "fingerfucked", + "fingerfucker", + "fingerfuckers", + "fingerfucking", + "fingerfucks", + "fistfuck", + "fistfucked", + "fistfucker", + "fistfuckers", + "fistfucking", + "fistfuckings", + "fistfucks", + "flange", + "fook", + "fooker", + "fuck", + "fucka", + "fucked", + "fucker", + "fuckers", + "fuckhead", + "fuckheads", + "fuckin", + "fucking", + "fuckings", + "fuckingshitmotherfucker", + "fuckme", + "fucks", + "fuckwhit", + "fuckwit", + "fudge packer", + "fudgepacker", + "fuk", + "fuker", + "fukker", + "fukkin", + "fuks", + "fukwhit", + "fukwit", + "fux", + "fux0r", + "f_u_c_k", + "gangbang", + "gangbanged", + "gangbangs", + "gaylord", + "gaysex", + "goatse", + "hardcoresex", + "heshe", + "hoar", + "hoare", + "hoer", + "homo", + "hore", + "hotsex", + "jism", + "jiz", + "jizm", + "jizz", + "kawk", + "knob", + "knobead", + "knobed", + "knobend", + "knobhead", + "knobjocky", + "knobjokey", + "kock", + "kondum", + "kondums", + "kunilingus", + "l3i+ch", + "l3itch", + "labia", + "m0f0", + "m0fo", + "m45terbate", + "ma5terb8", + "ma5terbate", + "masochist", + "master-bate", + "masterb8", + "masterbat*", + "masterbat3", + "masterbate", + "masterbation", + "masterbations", + "masturbate", + "mothafuck", + "mothafucka", + "mothafuckas", + "mothafuckaz", + "mothafucked", + "mothafucker", + "mothafuckers", + "mothafuckin", + "mothafucking", + "mothafuckings", + "mothafucks", + "mother fucker", + "motherfuck", + "motherfucked", + "motherfucker", + "motherfuckers", + "motherfuckin", + "motherfucking", + "motherfuckings", + "motherfuckka", + "motherfucks", + "muff", + "mutha", + "muthafecker", + "muthafuckker", + "muther", + "mutherfucker", + "numbnuts", + "nutsack", + "orgasim", + "orgasims", + "orgasm", + "orgasms", + "p0rn", + "pecker", + "penis", + "penisfucker", + "phonesex", + "phuck", + "phuk", + "phuked", + "phuking", + "phukked", + "phukking", + "phuks", + "phuq", + "pigfucker", + "pimpis", + "porno", + "pornography", + "pornos", + "prick", + "pricks", + "pron", + "pube", + "pusse", + "pussi", + "pussies", + "pussy", + "pussys", + "rectum", + "retard", + "rimjaw", + "rimming", + "s hit", + "s.o.b.", + "screwing", + "scroat", + "scrote", + "scrotum", + "semen", + "sex", + "sh!+", + "sh!t", + "sh1t", + "shag", + "shagger", + "shaggin", + "shagging", + "shemale", + "shi+", + "shit", + "shitdick", + "shite", + "shited", + "shitey", + "shitfuck", + "shitfull", + "shithead", + "shiting", + "shitings", + "shits", + "shitted", + "shitter", + "shitters", + "shitting", + "shittings", + "shitty", + "slut", + "sluts", + "smegma", + "smut", + "son-of-a-bitch", + "s_h_i_t", + "t1tt1e5", + "t1tties", + "teets", + "testical", + "testicle", + "tit", + "titfuck", + "tits", + "titt", + "tittie5", + "tittiefucker", + "titties", + "tittyfuck", + "tittywank", + "titwank", + "tosser", + "turd", + "tw4t", + "twat", + "twathead", + "twatty", + "twunt", + "twunter", + "v14gra", + "v1gra", + "vagina", + "viagra", + "vulva", + "w00se", + "wang", + "wank", + "wanker", + "wanky", + "whoar", + "whore", + "willies", + "willy", + "xrated" + ] +} diff --git a/src/Utils/logger.ts b/src/utils/Logger.ts similarity index 95% rename from src/Utils/logger.ts rename to src/utils/Logger.ts index 5e79d19d..c0f0c98a 100644 --- a/src/Utils/logger.ts +++ b/src/utils/Logger.ts @@ -26,7 +26,7 @@ const combinedFormat = format.combine( custom, ); -const logger = createLogger({ +export default createLogger({ format: combinedFormat, transports: [ new transports.File({ filename: 'logs/discord.log', format: infoFormat }), @@ -35,4 +35,3 @@ const logger = createLogger({ ], }); -export default logger; diff --git a/src/Utils/paginator.ts b/src/utils/Pagination.ts similarity index 58% rename from src/Utils/paginator.ts rename to src/utils/Pagination.ts index 446611ab..bc7d3071 100644 --- a/src/Utils/paginator.ts +++ b/src/utils/Pagination.ts @@ -1,5 +1,13 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, CommandInteraction, ComponentType, ButtonInteraction } from 'discord.js'; -import emojis from './JSON/emoji.json'; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + CommandInteraction, + ComponentType, + ButtonInteraction, +} from 'discord.js'; +import { emojis } from './Constants.js'; export interface PaginatorOptions { /** Number in milliseconds */ @@ -9,7 +17,7 @@ export interface PaginatorOptions { actionRow: ActionRowBuilder[]; updateComponents?(pageNumber: number): ActionRowBuilder; execute(i: ButtonInteraction): void; - } + }; btnEmojis?: { back: string; exit: string; @@ -17,33 +25,51 @@ export interface PaginatorOptions { }; } -export async function paginate(interaction: CommandInteraction, pages: EmbedBuilder[], options?: PaginatorOptions) { +export async function paginate( + interaction: CommandInteraction, + pages: EmbedBuilder[], + options?: PaginatorOptions, +) { if (pages.length < 1) { - interaction.reply({ content: `${emojis.normal.tick} No more pages to display!`, ephemeral: true }); + interaction.reply({ content: `${emojis.tick} No more pages to display!`, ephemeral: true }); return; } - const emojiBack = options?.btnEmojis?.back || emojis.normal.back; - const emojiExit = options?.btnEmojis?.exit || '🛑'; - const emojiNext = options?.btnEmojis?.next || emojis.normal.forward; + const emojiBack = options?.btnEmojis?.back ?? emojis.back; + const emojiExit = options?.btnEmojis?.exit ?? '🛑'; + const emojiNext = options?.btnEmojis?.next ?? emojis.forward; let index = 0; const row = new ActionRowBuilder().addComponents([ - new ButtonBuilder().setEmoji(emojiBack).setCustomId('1').setStyle(ButtonStyle.Primary).setDisabled(true), - new ButtonBuilder().setEmoji(emojiExit).setCustomId('3').setStyle(ButtonStyle.Danger).setLabel(`Page ${index + 1} of ${pages.length}`), - new ButtonBuilder().setEmoji(emojiNext).setCustomId('2').setStyle(ButtonStyle.Primary).setDisabled(pages.length <= index + 1), + new ButtonBuilder() + .setEmoji(emojiBack) + .setCustomId('1') + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setEmoji(emojiExit) + .setCustomId('3') + .setStyle(ButtonStyle.Danger) + .setLabel(`Page ${index + 1} of ${pages.length}`), + new ButtonBuilder() + .setEmoji(emojiNext) + .setCustomId('2') + .setStyle(ButtonStyle.Primary) + .setDisabled(pages.length <= index + 1), ]); const components: ActionRowBuilder[] = [row]; - if (options?.extraComponent) components.push(...options.extraComponent.actionRow); const data = { embeds: [pages[index]], components, }; - const listMessage = interaction.replied || interaction.deferred ? await interaction.followUp(data) : await interaction.reply(data); + const listMessage = + interaction.replied || interaction.deferred + ? await interaction.followUp(data) + : await interaction.reply(data); const col = listMessage.createMessageComponentCollector({ idle: options?.stopAfter || 60000, @@ -85,5 +111,4 @@ export async function paginate(interaction: CommandInteraction, pages: EmbedBuil col.on('end', () => { listMessage.edit({ components: [] }); }); - } diff --git a/src/utils/Profanity.ts b/src/utils/Profanity.ts new file mode 100644 index 00000000..d0ec5577 --- /dev/null +++ b/src/utils/Profanity.ts @@ -0,0 +1,46 @@ +// import { EmbedBuilder, User, TextChannel } from 'discord'; +// import { constants, getGuildName, getHubName } from './utils'; +import { createRequire } from 'node:module'; +import badwordsType from './JSON/profanity.json'; + +// create a require a ESM doesn't support importing JSON +const require = createRequire(import.meta.url); +const badwords = require('./JSON/profanity.json') as typeof badwordsType; + +/** + * Checks if a message contains any bad words. +*/ +export function check(string: string | undefined) { + if (!string) return false; + return badwords.profanity.some(word => string.split(/\b/).some(w => w.toLowerCase() === word.toLowerCase())); +} + +/** + * If the message contains bad words, it will be censored with asterisk(*). + * + * Code referenced from [`@web-mech/badwords`](https://github.com/web-mech/badwords). +*/ +export function censor(message: string): string { + const splitRegex = /\b/; + const specialChars = /[^a-zA-Z0-9|$|@]|\^/g; + const matchWord = /\w/g; + // filter bad words from message + // and replace it with * + return message.split(splitRegex).map(word => { + return check(word) ? word.replace(specialChars, '').replace(matchWord, '\\*') : word; + }).join(splitRegex.exec(message)?.at(0)); +} + + +/** A function that can be used to send a log of an ***uncensored*** message to the log channel. */ +// export async function log(rawContent: string, author: User, guildId: string | null, hubId: string) { +// const logChan = await author.client.channels.fetch(constants.channel.networklogs) as TextChannel; +// const hubName = await getHubName(hubId).catch(() => 'Unknown'); +// const guildName = getGuildName(author.client, guildId); +// const logEmbed = new EmbedBuilder() +// .setAuthor({ name: `${author.client.user?.username} logs`, iconURL: author.client.user?.avatarURL()?.toString() }) +// .setTitle('Bad Word Detected') +// .setColor(constants.colors.invisible) +// .setDescription(`||${rawContent}||\n\n**Author:** @${author.username} \`(${author.id})\`\n**Server:** ${guildName} (${guildId})\n**Hub:** ${hubName}`); +// return await logChan?.send({ embeds: [logEmbed] }); +// } diff --git a/src/utils/RegisterCommands.ts b/src/utils/RegisterCommands.ts new file mode 100644 index 00000000..3d09df26 --- /dev/null +++ b/src/utils/RegisterCommands.ts @@ -0,0 +1,48 @@ +import Logger from './Logger.js'; +import CommandHandler from '../structures/CommandHandler.js'; +import { REST, Routes } from 'discord.js'; +import { CLIENT_ID, SUPPORT_SERVER_ID } from './Constants.js'; +import { commandsMap } from '../commands/Command.js'; +import 'dotenv/config'; + +export default async function registerAllCommands(staffOnly = false) { + // make sure CommandsMap is not empty + await CommandHandler.loadCommandFiles(); + + const rest = new REST().setToken(process.env.TOKEN as string); + + if (staffOnly) { + const commands = commandsMap + .filter((command) => command.staffOnly) + .map((command) => command.data); + + // register staff commands to the main guild + return await rest + .put(Routes.applicationGuildCommands(CLIENT_ID, SUPPORT_SERVER_ID), { body: commands }) + .then(() => Logger.info('Registered all staff application commands.')); + } + else { + const commands = commandsMap.map((command) => command.data); + + // register all other commands to the global application; + return await rest + .put(Routes.applicationCommands(CLIENT_ID), { body: commands }) + .then(() => Logger.info('Registered all public application commands.')); + } +} + +process.argv.forEach((arg) => { + if (arg === '--public') { + registerAllCommands().then(() => process.exit(0)); + } + else if (arg === '--staff') { + registerAllCommands(true).then(() => process.exit(0)); + } + else if (arg === '--help') { + Logger.info('Usage: node utils/DeployCmdCli.js [--public|--staff|--help]'); + process.exit(0); + } + else { + return; + } +}); \ No newline at end of file diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts new file mode 100644 index 00000000..b5d84dd0 --- /dev/null +++ b/src/utils/Utils.ts @@ -0,0 +1,52 @@ +import { Snowflake } from 'discord.js'; +import { URLs } from './Constants.js'; + +/** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ +export function msToReadable(milliseconds: number): string { + let totalSeconds = milliseconds / 1000; + const days = Math.floor(totalSeconds / 86400); + totalSeconds %= 86400; + const hours = Math.floor(totalSeconds / 3600); + totalSeconds %= 3600; + const minutes = Math.floor(totalSeconds / 60); + const seconds = Math.floor(totalSeconds % 60); + let readable; + + if (days == 0 && hours == 0 && minutes == 0) readable = `${seconds} seconds`; + else if (days == 0 && hours == 0) readable = `${minutes}m ${seconds}s`; + else if (days == 0) readable = `${hours}h, ${minutes}m ${seconds}s`; + else readable = `${days}d ${hours}h, ${minutes}m ${seconds}s`; + + return readable; +} + +export function wait(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** Sort the array based on the reaction counts */ +export function sortReactions(reactions: { [key: string]: string[] }): [string, string[]][] { + // before: { '👍': ['10201930193'], '👎': ['10201930193'] } + return Object + .entries(reactions) + .sort((a, b) => b[1].length - a[1].length); // => [ [ '👎', ['10201930193'] ], [ '👍', ['10201930193'] ] ] +} + +export async function hasVoted(userId: Snowflake): Promise { + if (!process.env.TOPGG_API_KEY) throw new TypeError('Missing TOPGG_API_KEY environment variable'); + + const res = await ( + await fetch(`${URLs.TOPGG_API}/check?userId=${userId}`, { + method: 'GET', + headers: { + Authorization: process.env.TOPGG_API_KEY, + }, + }) + ).json(); + + return !!res.voted; +} + +export function yesOrNoEmoji(option: unknown, yesEmoji: string, noEmoji: string) { + return option ? yesEmoji : noEmoji; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 9718cfdc..c1e7a7c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,30 +1,25 @@ { - // "include": ["./typings/**/*.d.ts"], // "./typings/**/*.d.ts" "compilerOptions": { "incremental": true, "composite": true, "target": "ESNext", - "module": "commonjs", - "moduleResolution": "Node", + "module": "NodeNext", + "moduleResolution": "NodeNext", "resolveJsonModule": true, "allowJs": false, - "rootDir": "src", - "outDir": "build", + "declaration": true, + "rootDir": "./src", + "outDir": "./build", "esModuleInterop": true, "strict": true, "sourceMap": true, "inlineSources": true, - "forceConsistentCasingInFileNames": true, "skipLibCheck": true, - - // Set `sourceRoot` to "/src/" to strip the build path prefix - // from generated source code references. - // This improves issue grouping in Sentry. - "sourceRoot": "/src/" + "sourceRoot": "/src/", + "useUnknownInCatchVariables": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true }, - "include": [ - "src/**/*.ts", - "src/**/*.json", - // "typings/**/*" - ] + "include": ["src/**/*.ts", "src/**/*.json",], + "exclude": ["node_modules"], } diff --git a/typings/discord.d.ts b/typings/discord.d.ts deleted file mode 100644 index 020dcf2e..00000000 --- a/typings/discord.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import discord from 'discord.js'; - -type InterchatCommand = { - developer?: boolean, - staff?: boolean, - /* Cooldown in milliseconds */ - cooldown?: number, - description?: string | undefined - directory: string, - data: discord.SlashCommandBuilder | discord.ContextMenuCommandBuilder, - execute: (interaction: discord.ChatInputCommandInteraction | discord.ContextMenuCommandInteraction) => Promise, - autocomplete?: (interaction: discord.AutocompleteInteraction) => unknown -} - -declare module 'discord.js' { - export interface Client { - commands: discord.Collection; - description: string; - version: string; - commandCooldowns: discord.Collection<`${string}-${discord.Snowflake}`, number>; - reactionCooldowns: discord.Collection; - /* A generated invite link for the bot */ - invite: string; - } -} From 3797c4ccdde747ed3b1bdf55fd15b37d6ca3e058 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:37:35 +0530 Subject: [PATCH 02/13] added pagination, subcommands (partial), some hub commands, info commands & reactions --- README.md | 3 + src/InterChat.ts | 15 +- src/SuperClient.ts | 13 +- src/commands/Command.ts | 50 ++-- src/commands/slash/Information/help.ts | 239 +++++++++++++++ src/commands/slash/Information/invite.ts | 41 +++ src/commands/slash/Information/rules.ts | 13 + src/commands/slash/Information/stats.ts | 1 - src/commands/slash/Information/vote.ts | 36 +++ src/commands/slash/Main/connection.ts | 58 ++-- src/commands/slash/Main/hub.ts | 45 ++- src/commands/subcommands/hub/delete.ts | 102 +++++++ src/commands/subcommands/hub/join.ts | 144 +++++++++ src/commands/subcommands/hub/manage.ts | 364 +++++++++++++++++++++++ src/commands/subcommands/hub/settings.ts | 137 +++++++++ src/decorators/Interaction.ts | 2 + src/index.ts | 4 +- src/scripts/network/onboarding.ts | 106 +++++++ src/structures/BlacklistManager.ts | 4 +- src/structures/CommandHandler.ts | 63 ---- src/structures/CommandManager.ts | 88 ++++++ src/structures/CustomID.ts | 23 +- src/structures/NetworkManager.ts | 166 +++++++---- src/structures/Scheduler.ts | 2 +- src/typings/index.d.ts | 9 +- src/updater/ReactionUpdater.ts | 210 ++++++++++--- src/utils/Constants.ts | 7 +- src/utils/Pagination.ts | 12 +- src/utils/RegisterCommands.ts | 4 +- src/utils/Utils.ts | 104 ++++++- 30 files changed, 1777 insertions(+), 288 deletions(-) create mode 100644 src/commands/slash/Information/help.ts create mode 100644 src/commands/slash/Information/invite.ts create mode 100644 src/commands/slash/Information/rules.ts create mode 100644 src/commands/slash/Information/vote.ts create mode 100644 src/commands/subcommands/hub/delete.ts create mode 100644 src/commands/subcommands/hub/join.ts create mode 100644 src/commands/subcommands/hub/manage.ts create mode 100644 src/commands/subcommands/hub/settings.ts create mode 100644 src/scripts/network/onboarding.ts delete mode 100644 src/structures/CommandHandler.ts create mode 100644 src/structures/CommandManager.ts diff --git a/README.md b/README.md index 93ae8830..620f263a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # InterChat Code for a growing discord bot which provides a fun inter-server chat! +// subcommand files must have the same name as the subcommand registered on discord +// commands and subcommands must be default exports + ## Starting the bot: 1. Install dependencies using `npm install` 2. Make a file called `.env` and fill it out with the appropriate contents mentioned in the env.example file. diff --git a/src/InterChat.ts b/src/InterChat.ts index dd049801..f9c696f9 100644 --- a/src/InterChat.ts +++ b/src/InterChat.ts @@ -1,6 +1,6 @@ import db from './utils/Db.js'; import SuperClient from './SuperClient.js'; -import CommandHandler from './structures/CommandHandler.js'; +import CommandManager from './structures/CommandManager.js'; import { NetworkMessage } from './structures/NetworkManager.js'; class InterChat extends SuperClient { @@ -12,7 +12,7 @@ class InterChat extends SuperClient { this.init(); // load commands - CommandHandler.loadCommandFiles(); + CommandManager.loadCommandFiles(); this.logger.info( `Logged in as ${this.user?.tag}! Cached ${this.guilds.cache.size} guilds on Cluster ${this.cluster?.id}.`, @@ -29,20 +29,13 @@ class InterChat extends SuperClient { this.on('interactionCreate', (interaction) => this.getCommandManager().handleInteraction(interaction)); // handle network reactions - this.on('messageReactionAdd', (reaction, user) => this.getReactionUpdater().listen(reaction, user)); + this.on('messageReactionAdd', (reaction, user) => this.getReactionUpdater().listenForReactions(reaction, user)); // handle network messages this.on('messageCreate', async (message) => { if (message.author.bot || message.system || message.webhookId) return; - const isNetworkMessage = await db.connectedList.findFirst({ - where: { channelId: message.channel.id, connected: true }, - include: { hub: true }, - }); - - if (!isNetworkMessage) return; - - this.getNetworkManager().handleNetworkMessage(message as NetworkMessage, isNetworkMessage); + this.getNetworkManager().handleNetworkMessage(message as NetworkMessage); }); } diff --git a/src/SuperClient.ts b/src/SuperClient.ts index 8344710d..8ce52094 100644 --- a/src/SuperClient.ts +++ b/src/SuperClient.ts @@ -8,14 +8,14 @@ import { Snowflake, } from 'discord.js'; import { ClusterClient, getInfo } from 'discord-hybrid-sharding'; -import { BlacklistManager } from './structures/BlacklistManager.js'; import { commandsMap, interactionsMap } from './commands/Command.js'; -import { Scheduler } from './structures/Scheduler.js'; import Logger from './utils/Logger.js'; -import CommandHandler from './structures/CommandHandler.js'; +import Scheduler from './structures/Scheduler.js'; +import NSFWClient from './structures/NSFWDetection.js'; +import CommandManager from './structures/CommandManager.js'; import NetworkManager from './structures/NetworkManager.js'; import ReactionUpdater from './updater/ReactionUpdater.js'; -import NSFWClient from './structures/NSFWDetection.js'; +import BlacklistManager from './structures/BlacklistManager.js'; export default abstract class SuperClient extends Client { readonly logger = Logger; @@ -30,7 +30,7 @@ export default abstract class SuperClient extends Client { readonly cluster = new ClusterClient(this); private readonly scheduler = new Scheduler(); - private readonly commandHandler = new CommandHandler(this); + private readonly commandHandler = new CommandManager(this); private readonly networkHandler = new NetworkManager(this); private readonly blacklistManager = new BlacklistManager(this.scheduler); private readonly reactionUpdater = new ReactionUpdater(this); @@ -55,6 +55,7 @@ export default abstract class SuperClient extends Client { IntentsBitField.Flags.GuildMessages, IntentsBitField.Flags.GuildMessageReactions, ], + presence: { status: 'invisible' }, }); } @@ -78,7 +79,7 @@ export default abstract class SuperClient extends Client { return fetch ? this.resolveEval(fetch) : undefined; } - getCommandManager(): CommandHandler { + getCommandManager(): CommandManager { return this.commandHandler; } getNetworkManager(): NetworkManager { diff --git a/src/commands/Command.ts b/src/commands/Command.ts index c5f26be9..96b5ddb6 100644 --- a/src/commands/Command.ts +++ b/src/commands/Command.ts @@ -7,56 +7,60 @@ import { ModalSubmitInteraction, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; +import { existsSync, readdirSync } from 'fs'; type CommandInteraction = ChatInputCommandInteraction | ContextMenuCommandInteraction; type InteractionFunction = ( interaction: MessageComponentInteraction | ModalSubmitInteraction, ) => void; -export interface CommandOptions { - readonly data: RESTPostAPIApplicationCommandsJSONBody; - readonly staffOnly?: boolean; - readonly description?: string; - readonly execute: (interaction: CommandInteraction) => Promise; - readonly autocomplete?: (interaction: AutocompleteInteraction) => Promise; -} - -export const commandsMap = new Collection(); +export const commandsMap = new Collection(); export const interactionsMap = new Collection(); export default abstract class Command { abstract readonly data: RESTPostAPIApplicationCommandsJSONBody; readonly staffOnly?: boolean; readonly description?: string; + // wait wtf + static readonly subcommands?: Collection; abstract execute(interaction: CommandInteraction): Promise; // optional methods // eslint-disable-next-line @typescript-eslint/no-unused-vars - handleComponent(interaction: MessageComponentInteraction): void { + handleComponent(interaction: MessageComponentInteraction) { /**/ } // eslint-disable-next-line @typescript-eslint/no-unused-vars - handleModal(interaction: ModalSubmitInteraction): void { + handleModal(interaction: ModalSubmitInteraction) { /**/ } // eslint-disable-next-line @typescript-eslint/no-unused-vars - autocomplete(interaction: AutocompleteInteraction): void { + autocomplete(interaction: AutocompleteInteraction) { /**/ } - loadCommand() { - // Save command to the `clientCommands` map - commandsMap.set(this.data.name, this.toJSON()); + loadSubcommands() { + const commandName = this.data.name; + const fullPath = `build/commands/subcommands/${commandName}/`; + if (!existsSync(fullPath)) return; + + readdirSync(fullPath) + .forEach(async (file) => { + if (file.endsWith('.js')) { + const subcommandFile = (await import(`../commands/subcommands/${commandName}/${file}`)).default; + const subcommandInstance = new subcommandFile() as Command; + const parentCommand = Object.getPrototypeOf(subcommandInstance.constructor); + + // create a new instance of the subcommand class + // and set it in the subcommands map of the parent command + parentCommand.subcommands.set(file.replace('.js', ''), subcommandInstance); + } + }); } - toJSON() { - return { - data: this.data, - staffOnly: this.staffOnly, - description: this.description, - execute: this.execute, - autocomplete: this.autocomplete, - } as CommandOptions; + /** Save command to the `clientCommands` map */ + loadCommand() { + commandsMap.set(this.data.name, this); } } diff --git a/src/commands/slash/Information/help.ts b/src/commands/slash/Information/help.ts new file mode 100644 index 00000000..322fe196 --- /dev/null +++ b/src/commands/slash/Information/help.ts @@ -0,0 +1,239 @@ +import { stripIndents } from 'common-tags'; +import { + EmbedBuilder, + ActionRowBuilder, + StringSelectMenuBuilder, + ButtonBuilder, + ButtonStyle, + User, + ChatInputCommandInteraction, + StringSelectMenuInteraction, +} from 'discord.js'; +import { colors, emojis } from '../../../utils/Constants.js'; +import Command from '../../Command.js'; +import { getCredits, setComponentExpiry } from '../../../utils/Utils.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; + +export default class Help extends Command { + readonly data = { + name: 'help', + description: 'Shows all commands (soon) and guides for InterChat.', + }; + + async execute(interaction: ChatInputCommandInteraction) { + const embed = new EmbedBuilder() + .setColor(colors.interchatBlue) + .setThumbnail(interaction.client.user.avatarURL()) + .setDescription( + stripIndents` + ## InterChat Help + InterChat is a powerful discord bot that enables effortless cross-server chatting! Get started by looking at the categories below. + ### Categories: + - ${emojis.slashCommand} [**All Commands**](https://discord-interchat.github.io/docs/category/commands) + - 👥 [**InterChat Hubs**](https://discord-interchat.github.io/docs/hub/joining) + - ⚙️ [**Setting up InterChat**](https://discord-interchat.github.io/docs/setup) + - 💬 [**Messaging & Network**](https://discord-interchat.github.io/docs/messaging) + `, + ) + .setFooter({ + text: `Requested by @${interaction.user.username}`, + iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, + }); + + const selects = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder({ + customId: new CustomID('credits:guide', [interaction.user.id]) + .setIdentifier('credits', 'guide') + .addData(interaction.user.id) + .toString(), + options: [ + { + label: 'Hubs', + value: 'hubs', + emoji: '👥', + description: 'How to join, leave, create, delete and use Hubs.', + }, + { + label: 'Network', + value: 'network', + emoji: '🌐', + description: 'How the InterChat network (Inter-Server Chat) works.', + }, + { + label: 'Messaging', + value: 'messaging', + emoji: '💬', + description: 'How to send, edit, delete and react to network messages!', + }, + { + label: 'Commands', + value: 'commands', + emoji: emojis.slashCommand, + description: 'View all of InterChat\'s commands.', + }, + { + label: 'The Team', + value: 'credits', + emoji: emojis.wand, + description: 'Learn more about the team behind InterChat!', + }, + ], + placeholder: 'Select a Category...', + }), + ); + + const linkButtons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel('Invite') + .setURL('https://discord.com/application-directory/769921109209907241'), + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel('Guide') + .setURL('https://discord-interchat.github.io/docs'), + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel('Support') + .setURL('https://discord.gg/6bhXQynAPs'), + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel('Vote me!') + .setURL('https://top.gg/bot/769921109209907241/vote'), + ); + + await interaction.reply({ + embeds: [embed], + components: [selects, linkButtons], + }); + + // Disable the components after 10 minutes + setComponentExpiry( + interaction.client.getScheduler(), + await interaction.fetchReply(), + 60 * 15000, + ); + } + + @ComponentInteraction('credits') + async handleComponent(interaction: StringSelectMenuInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + if (interaction.user.id !== customId.data[0]) { + await interaction.reply({ + content: 'This button is not for you.', + ephemeral: true, + }); + } + + const templateEmbed = new EmbedBuilder() + .setColor(colors.interchatBlue) + .setThumbnail(interaction.client.user.avatarURL()) + .setFooter({ + text: `Requested by @${interaction.user.username}`, + iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, + }); + + switch (interaction.values[0]) { + case 'hubs': { + const hubsEmbed = EmbedBuilder.from(templateEmbed).setDescription(stripIndents` + ## InterChat Hubs + Think of hubs as your personal chat spaces, both creatable and joinable. They're like an exclusive room, where other servers can join to engage and chat together. + + ### Hub Guides + - ${emojis.join} [**Join a Hub**](https://discord-interchat.github.io/docs/hub/joining) + - ${emojis.leave} [**Leave a Hub**](https://discord-interchat.github.io/docs/hub/leaving) + - ✨ [**Create a Hub**](https://discord-interchat.github.io/docs/hub/management#creating-a-hub) + - ${emojis.delete} [**Delete a Hub**](https://discord-interchat.github.io/docs/hub/management#deleting-a-hub) + - 🛡️ [**Hub Moderators (Adding, Removing)**](https://discord-interchat.github.io/docs/hub/management#managing-hub-moderators) + - 📝 [**Edit Hub**](https://discord-interchat.github.io/docs/hub/management#editing-your-hub) + - ${emojis.settings} [**Hub Settings**](https://discord-interchat.github.io/docs/hub/management#hub-settings) + `); + + await interaction.update({ embeds: [hubsEmbed] }); + break; + } + case 'network': { + const networkEmbed = EmbedBuilder.from(templateEmbed).setDescription(stripIndents` + ## The Network + Network refers to the entire web of servers that are connected to a hub. In this area, you can send messages that will appear in other channels that have been set up on other servers. + ### Network Guides + - 🌎 [**What is the network?** (Coming Soon!)](https://discord-interchat.github.io/docs/hub/network) + - ${emojis.connect} [**Using the network**](https://discord-interchat.github.io/docs/messaging#sending-messages) + - ${emojis.settings} [**Network Settings (Coming soon!)**](https://discord-interchat.github.io/docs/hub/network#network-settings) + `); + + await interaction.update({ embeds: [networkEmbed] }); + break; + } + + case 'messaging': { + const messagingEmbed = EmbedBuilder.from(templateEmbed).setDescription(stripIndents` + ## Messaging + Messaging refers to the ability to send messages to other servers within a hub. Find out how to send messages by visiting the guides below. You can also edit and delete messages that you have sent. + ### Messaging Guides + - 📨 [**Send Messages**](https://discord-interchat.github.io/docs/messaging#sending-messages) + - ✏️ [**Edit Messages**](https://discord-interchat.github.io/docs/messaging#editing-messages) + - ${emojis.delete} [**Delete Messages**](https://discord-interchat.github.io/docs/messaging#deleting-messages) + - ${emojis.reply} [**Reply to Messages**](https://discord-interchat.github.io/docs/messaging#replying-to-messages) + - 😂 [**React to Messages (Coming soon!)**](https://discord-interchat.github.io/docs/message/reacting) + - ${emojis.wand} [**The InterChat Team (Coming Soon!)**](https://discord-interchat.github.io/docs/credits) + - 📑 [**Report Messages**](https://discord-interchat.github.io/docs/messaging#reporting-messages--users) + `); + + await interaction.update({ embeds: [messagingEmbed] }); + break; + } + case 'commands': { + await interaction.reply('no'); + break; + } + case 'credits': { + await interaction.deferUpdate(); + + const members: User[] = []; + const credits = getCredits(); + for (const credit of credits) { + const shardValues = (await interaction.client.cluster.broadcastEval( + `this.users.cache.get('${credit}')`, + { context: { userId: credit } }, + )) as User[]; + + const member = + shardValues.find((m) => !!m) ?? (await interaction.client.users.fetch(credit)); + + members.push(member); + } + + const linksDivider = `${emojis.blueLine.repeat(9)} **LINKS** ${emojis.blueLine.repeat(9)}`; + const creditsDivider = `${emojis.blueLine.repeat(9)} **TEAM** ${emojis.blueLine.repeat(9)}`; + + const creditsEmbed = EmbedBuilder.from(templateEmbed).setDescription(` + ## ${emojis.wand} The Team + InterChat is a project driven by a passionate team dedicated to enhancing the Discord experience. We welcome new members to join our team; if you're interested, please join our support server. + + ${creditsDivider} + ${emojis.interchatCircle} **Design:** + ${emojis.dotBlue} @${members[6]?.username} (Mascot) + ${emojis.dotBlue} @${members[4]?.username} (Avatar) + ${emojis.dotBlue} @${members[0]?.username} (Avatar) + ${emojis.dotBlue} @${members[5]?.username} (Avatar & Server Icon) + + ${emojis.botdev} **Developers:** + ${emojis.dotBlue} @${members[1]?.username} + ${emojis.dotBlue} @${members[2]?.username} + ${emojis.dotBlue} @${members[0].username} + + ${emojis.staff} **Staff: (Recruiting!)** + ${emojis.dotBlue} @${members[4]?.username} + ${emojis.dotBlue} @${members[3]?.username} + ${emojis.dotBlue} @${members[5]?.username} + + ${linksDivider} + [Guide](https://discord-interchat.github.io/docs) • [Invite](https://discord.com/application-directory/769921109209907241) • [Support Server](https://discord.gg/6bhXQynAPs) • [Vote](https://top.gg/bot/769921109209907241/vote) • [Privacy](https://discord-interchat.github.io/legal/privacy) • [Terms](https://discord-interchat.github.io/legal/terms) + `); + + await interaction.editReply({ embeds: [creditsEmbed] }); + } + } + } +} diff --git a/src/commands/slash/Information/invite.ts b/src/commands/slash/Information/invite.ts new file mode 100644 index 00000000..df0afcec --- /dev/null +++ b/src/commands/slash/Information/invite.ts @@ -0,0 +1,41 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChatInputCommandInteraction, + OAuth2Scopes, +} from 'discord.js'; +import Command from '../../Command.js'; +import { emojis } from '../../../utils/Constants.js'; +import { stripIndents } from 'common-tags'; + +export default class Invite extends Command { + readonly data = { + name: 'invite', + description: 'Invite me to your server!', + }; + async execute(interaction: ChatInputCommandInteraction) { + const inviteLink = interaction.client.generateInvite({ + scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands], + // FIXME: Update the permissions every time you update invite + permissions: 292662144192n, + }); + + const InviteButtons = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setLabel('Invite Me!') + .setURL(inviteLink) + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.invite) + .setDisabled(false), + ]); + await interaction.reply({ + content: stripIndents` + Thank you for choosing to invite InterChat. Simply click the button below to invite me! + ! + + - **__Support Server__:** https://discord.gg/6bhXQynAPs`, + components: [InviteButtons], + }); + } +} diff --git a/src/commands/slash/Information/rules.ts b/src/commands/slash/Information/rules.ts new file mode 100644 index 00000000..a640229e --- /dev/null +++ b/src/commands/slash/Information/rules.ts @@ -0,0 +1,13 @@ +import { ChatInputCommandInteraction } from 'discord.js'; +import { rulesEmbed } from '../../../utils/Constants.js'; +import Command from '../../Command.js'; + +export default class Rules extends Command { + readonly data = { + name: 'rules', + description: 'Sends the network rules for InterChat.', + }; + async execute(interaction: ChatInputCommandInteraction) { + await interaction.reply({ embeds: [rulesEmbed] }); + } +} \ No newline at end of file diff --git a/src/commands/slash/Information/stats.ts b/src/commands/slash/Information/stats.ts index d4070490..7afd56dd 100644 --- a/src/commands/slash/Information/stats.ts +++ b/src/commands/slash/Information/stats.ts @@ -90,7 +90,6 @@ export default class Stats extends Command { const linksRow = new ActionRowBuilder().addComponents( new ButtonBuilder().setLabel('Support').setStyle(ButtonStyle.Link).setURL(supportServer), new ButtonBuilder().setLabel('Guide').setStyle(ButtonStyle.Link).setURL(docsLink), - new ButtonBuilder().setLabel('Guido').setStyle(ButtonStyle.Primary).setCustomId('guide'), new ButtonBuilder() .setLabel('Invite') .setStyle(ButtonStyle.Link) diff --git a/src/commands/slash/Information/vote.ts b/src/commands/slash/Information/vote.ts new file mode 100644 index 00000000..63fb8b41 --- /dev/null +++ b/src/commands/slash/Information/vote.ts @@ -0,0 +1,36 @@ +import { stripIndents } from 'common-tags'; +import { ChatInputCommandInteraction, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import Command from '../../Command.js'; +import { colors } from '../../../utils/Constants.js'; + +export default class Vote extends Command { + readonly data = { + name: 'vote', + description: 'Voting perks and vote link.', + }; + async execute(interaction: ChatInputCommandInteraction) { + const embed = new EmbedBuilder() + .setDescription(stripIndents` + ## 🗳️ Vote for InterChat and Enjoy Exclusive Perks + Your contribution is invaluable in elevating InterChat's position on Top.gg. Each and every vote makes a significant difference! + + As our way of expressing gratitude for your support, we are thrilled to offer you exclusive advantages. By casting your vote for InterChat, you'll unlock: + + - Edit messages within hubs + - Translating messages (and much more on the way!) + + We deeply appreciate your unwavering support. Thank you! 🙏 + `) + .setColor(colors.interchatBlue); + + const button = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel('Vote!') + .setEmoji('🗳️') + .setURL('https://top.gg/bot/769921109209907241/vote'), + ); + + await interaction.reply({ embeds: [embed], components: [button] }); + } +} \ No newline at end of file diff --git a/src/commands/slash/Main/connection.ts b/src/commands/slash/Main/connection.ts index 5f524ffe..f68b1378 100644 --- a/src/commands/slash/Main/connection.ts +++ b/src/commands/slash/Main/connection.ts @@ -13,8 +13,10 @@ import { RESTPostAPIApplicationCommandsJSONBody, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, + TextChannel, TextInputBuilder, TextInputStyle, + ThreadChannel, } from 'discord.js'; import Command from '../../Command.js'; import db from '../../../utils/Db.js'; @@ -23,6 +25,7 @@ import { buildEmbed } from '../../../scripts/network/buildEmbed.js'; import { buildConnectionButtons } from '../../../scripts/network/components.js'; import { emojis } from '../../../utils/Constants.js'; import { CustomID } from '../../../structures/CustomID.js'; +import { disableComponents, getOrCreateWebhook } from '../../../utils/Utils.js'; export default class Connection extends Command { readonly data: RESTPostAPIApplicationCommandsJSONBody = { @@ -33,17 +36,17 @@ export default class Connection extends Command { options: [ { type: ApplicationCommandOptionType.String, - name: 'connection', - description: 'Choose a connection.', + name: 'channel', + description: 'Choose a connection to manage.', required: true, autocomplete: true, }, ], }; async execute(interaction: ChatInputCommandInteraction): Promise { - const connection = interaction.options.getString('connection', true); const networkManager = interaction.client.getNetworkManager(); - const isInDb = await networkManager.fetchConnection({ channelId: connection }); + const channelId = interaction.options.getString('channel', true); + const isInDb = await networkManager.fetchConnection({ channelId }); if (!isInDb) { interaction.reply({ @@ -53,8 +56,8 @@ export default class Connection extends Command { return; } - const embed = await buildEmbed(interaction, connection); - const buttons = buildConnectionButtons(true, connection, { userId: interaction.user.id }); + const embed = await buildEmbed(interaction, channelId); + const buttons = buildConnectionButtons(true, channelId, { userId: interaction.user.id }); if (!interaction.deferred && !interaction.replied) await interaction.deferReply(); @@ -63,7 +66,7 @@ export default class Connection extends Command { .setCustomId( new CustomID() .setIdentifier('connection', 'settings') - .addData(connection) + .addData(channelId) .addData(interaction.user.id) .toString(), ) @@ -97,11 +100,11 @@ export default class Connection extends Command { ), ]); - const channelExists = await interaction.client.channels.fetch(connection).catch(() => null); + const channelExists = await interaction.guild?.channels.fetch(channelId).catch(() => null); if (!channelExists) { await networkManager.updateConnection( - { channelId: connection }, + { channelId: channelId }, { connected: !isInDb.connected }, ); await interaction.followUp({ @@ -143,7 +146,7 @@ export default class Connection extends Command { @ComponentInteraction('connection') async handleComponent(interaction: MessageComponentInteraction) { - const customId = CustomID.toJSON(interaction.customId); + const customId = CustomID.parseCustomId(interaction.customId); const channelId = customId.data[0]; if (customId.data.at(1) && customId.data[1] !== interaction.user.id) { @@ -164,12 +167,13 @@ export default class Connection extends Command { const isInDb = await networkManager.fetchConnection({ channelId }); if (!isInDb || !channelId) { await interaction.reply({ - content: `${emojis.no} This connection does not exist.`, + content: `${emojis.no} This connection no longer exists.`, ephemeral: true, }); return; } + // button interactions if (interaction.isButton() && customId.postfix === 'toggle') { const toggleRes = await networkManager.updateConnection( { channelId }, @@ -180,10 +184,12 @@ export default class Connection extends Command { embeds: [await buildEmbed(interaction, channelId)], components: [ interaction.message.components[0], - await buildConnectionButtons(toggleRes?.connected, channelId), + buildConnectionButtons(toggleRes?.connected, channelId), ], }); } + + // String select menu interactions else if (interaction.isStringSelectMenu()) { switch (interaction.values[0]) { case 'compact': @@ -240,6 +246,12 @@ export default class Connection extends Command { components: [channelSelect], ephemeral: true, }); + + // current interaction will become outdated due to new channelId + await interaction.message.edit({ + content: `${emojis.info} Channel switch called, use the command again to view new connection.`, + components: disableComponents(interaction.message), + }); break; } @@ -254,30 +266,40 @@ export default class Connection extends Command { const newEmbeds = await buildEmbed(interaction, channelId); interaction.replied || interaction.deferred - ? await interaction.editReply({ embeds: [newEmbeds] }) + ? await interaction.message.edit({ embeds: [newEmbeds] }) : await interaction.update({ embeds: [newEmbeds] }); } + + // channel select menu interactions else if (interaction.isChannelSelectMenu()) { if (customId.postfix !== 'change_channel') return; const newChannel = interaction.channels.first(); - if (newChannel?.id === channelId) { + const channelInHub = await networkManager.fetchConnection({ channelId: newChannel?.id }); + if (channelInHub) { await interaction.reply({ - content: `${emojis.no} You cannot switch to the same channel.`, + content: `${emojis.no} Channel ${newChannel} is already connected to a hub.`, ephemeral: true, }); return; } - await networkManager.updateConnection({ channelId }, { channelId: newChannel?.id }); + const newWebhook = await getOrCreateWebhook(newChannel as TextChannel | ThreadChannel); + await networkManager.updateConnection( + { channelId }, + { channelId: newChannel?.id, webhookURL: newWebhook?.url }, + ); - await interaction.update(`${emojis.yes} Switched network channel to <#${newChannel?.id}>.`); + await interaction.update({ + content: `${emojis.yes} Switched network channel to <#${newChannel?.id}>.`, + components: [], + }); } } @ComponentInteraction('connectionModal') async handleModal(interaction: ModalSubmitInteraction): Promise { - const customId = CustomID.toJSON(interaction.customId); + const customId = CustomID.parseCustomId(interaction.customId); if (customId.identifier !== 'connectionModal') return; const invite = interaction.fields.getTextInputValue('connInviteField'); diff --git a/src/commands/slash/Main/hub.ts b/src/commands/slash/Main/hub.ts index 3080a978..5d3c924a 100644 --- a/src/commands/slash/Main/hub.ts +++ b/src/commands/slash/Main/hub.ts @@ -1,18 +1,14 @@ import { APIApplicationCommandBasicOption, - ActionRowBuilder, ApplicationCommandOptionType, AutocompleteInteraction, - ButtonBuilder, - ButtonStyle, + ChannelType, ChatInputCommandInteraction, - MessageComponentInteraction, + Collection, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; import Command from '../../Command.js'; import db from '../../../utils/Db.js'; -import { CustomID } from '../../../structures/CustomID.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; const hubOption: APIApplicationCommandBasicOption = { name: 'hub', @@ -22,7 +18,7 @@ const hubOption: APIApplicationCommandBasicOption = { autocomplete: true, }; -export default class Hub extends Command { +export default class HubCommand extends Command { readonly data: RESTPostAPIApplicationCommandsJSONBody = { name: 'hub', description: 'Manage your hubs.', @@ -34,7 +30,7 @@ export default class Hub extends Command { }, { type: ApplicationCommandOptionType.Subcommand, - name: 'edit', + name: 'manage', description: '📝 Edit a hub you own.', options: [hubOption], }, @@ -49,7 +45,14 @@ export default class Hub extends Command { name: 'join', description: '🔗 Join a public/private hub from this server.', options: [ - hubOption, + { + type: ApplicationCommandOptionType.Channel, + name: 'channel', + description: 'The channel you want to use connect to a hub.', + required: true, + channel_types: [ChannelType.GuildText, ChannelType.PublicThread, ChannelType.PrivateThread], + }, + { ...hubOption, required: false }, { type: ApplicationCommandOptionType.String, name: 'invite', @@ -96,29 +99,15 @@ export default class Hub extends Command { ], }; + // subcommand classes are added to this map in their respective files + static readonly subcommands = new Collection; + async execute(interaction: ChatInputCommandInteraction): Promise { - await interaction.reply({ - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId( - new CustomID() - .setIdentifier('hub', 'browse') - .toString(), - ) - .setLabel('Browse') - .setStyle(ButtonStyle.Primary), - ), - ], - }); + const subcommand = HubCommand.subcommands?.get(interaction.options.getSubcommand()); + subcommand?.execute(interaction); return; } - @ComponentInteraction('hub:browse') - async handleComponent(interaction: MessageComponentInteraction) { - interaction.reply('hello world!'); - } - async autocomplete(interaction: AutocompleteInteraction): Promise { const modCmds = ['manage', 'settings', 'connections', 'invite', 'moderator']; diff --git a/src/commands/subcommands/hub/delete.ts b/src/commands/subcommands/hub/delete.ts new file mode 100644 index 00000000..d426fbc4 --- /dev/null +++ b/src/commands/subcommands/hub/delete.ts @@ -0,0 +1,102 @@ +import { ChatInputCommandInteraction, CacheType, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, ButtonInteraction } from 'discord.js'; +import db from '../../../utils/Db.js'; +import HubCommand from '../../slash/Main/hub.js'; +import { captureException } from '@sentry/node'; +import { emojis } from '../../../utils/Constants.js'; +import { setComponentExpiry } from '../../../utils/Utils.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; + +export default class Delete extends HubCommand { + async execute(interaction: ChatInputCommandInteraction) { + const hubName = interaction.options.getString('hub', true); + const hubInDb = await db.hubs.findFirst({ where: { name: hubName } }); + + if (interaction.user.id !== hubInDb?.ownerId) { + return await interaction.reply({ + content: `${emojis.info} Unable to find hub. Make sure you are the owner of the hub.`, + ephemeral: true, + }); + } + + const confirmEmbed = new EmbedBuilder() + .setTitle('Are you sure?') + .setDescription('Are you sure you want to delete this hub? This is a destructive action that will **delete all connections** along with the hub.') + .setColor('Red'); + const confirmButtons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setLabel('Confirm') + .setCustomId( + new CustomID() + .setIdentifier('hub_delete', 'confirm') + .addData(interaction.user.id) + .addData(hubName) + .toString(), + ) + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setLabel('Cancel') + .setCustomId( + new CustomID() + .setIdentifier('hub_delete', 'cancel') + .addData(interaction.user.id) + .addData(hubName) + .toString(), + ) + .setStyle(ButtonStyle.Secondary), + ); + + await interaction.reply({ + embeds: [confirmEmbed], + components: [confirmButtons], + }); + + setComponentExpiry(interaction.client.getScheduler(), await interaction.fetchReply(), 10_000); + } + + @ComponentInteraction('hub_delete') + async handleComponent(interaction: ButtonInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + const userId = customId.data[0]; + const hubName = customId.data[1]; + + if (interaction.user.id !== userId) { + return await interaction.reply({ + content: 'Only the hub owner can delete this hub.', + ephemeral: true, + }); + } + + if (customId.postfix === 'cancel') { + await interaction.message.delete().catch(() => null); + return; + } + + const hubInDb = await db.hubs.findFirst({ where: { name: hubName, ownerId: interaction.user.id } }); + if (!hubInDb) { + return await interaction.update({ + content: `Hub **${hubName}** no longer exists.`, + embeds: [], + components: [], + }); + } + + await interaction.update(`${emojis.loading} Deleting connections, invites, messages and the hub. Please wait...`); + + try { + await db.hubs.delete({ where: { id: hubInDb.id } }); + } + catch (e) { + captureException(e, { user: { id: interaction.user.id, username: interaction.user.username } }); + await interaction.editReply('Something went wrong while trying to delete the hub. The developers have been notified.'); + return; + } + + await interaction.editReply({ + content:`${emojis.tick} The hub has been successfully deleted.`, + embeds: [], + components: [], + }); + } +} \ No newline at end of file diff --git a/src/commands/subcommands/hub/join.ts b/src/commands/subcommands/hub/join.ts new file mode 100644 index 00000000..2320ce4c --- /dev/null +++ b/src/commands/subcommands/hub/join.ts @@ -0,0 +1,144 @@ +import { ButtonInteraction, CacheType, ChannelType, ChatInputCommandInteraction } from 'discord.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { emojis } from '../../../utils/Constants.js'; +import HubCommand from '../../slash/Main/hub.js'; +import db from '../../../utils/Db.js'; +import BlacklistManager from '../../../structures/BlacklistManager.js'; +import { hubs } from '@prisma/client'; +import { getOrCreateWebhook } from '../../../utils/Utils.js'; +import { showOnboarding } from '../../../scripts/network/onboarding.js'; +import { stripIndents } from 'common-tags'; + +export default class JoinSubCommand extends HubCommand { + async execute(interaction: ChatInputCommandInteraction): Promise { + if (!interaction.inCachedGuild()) { + return await interaction.reply({ + content: `${emojis.no} This command can only be used in servers!`, + ephemeral: true, + }); + } + + const networkManager = interaction.client.getNetworkManager(); + const hubName = interaction.options.getString('hub') ?? 'InterChat Central'; + const invite = interaction.options.getString('invite'); + const channel = interaction.options.getChannel('channel', true, [ + ChannelType.GuildText, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ]); + + const channelInHub = await networkManager.fetchConnection({ channelId: channel.id }); + if (channelInHub) { + return await interaction.reply({ + content: `${emojis.no} You are already connected to a hub from ${channel}.`, + ephemeral: true, + }); + } + + let hub: hubs | null = null; + + // bunch of checks to see if hub exists / invite is valid + if (invite) { + const fetchedInvite = await db.hubInvites.findFirst({ + where: { code: invite }, + include: { hub: true }, + }); + + if (!fetchedInvite) { + return await interaction.reply({ + content: `${emojis.no} That invite code is invalid.`, + ephemeral: true, + }); + } + + hub = fetchedInvite.hub; + } + else { + hub = await db.hubs.findFirst({ where: { name: hubName } }); + + if (!hub) { + return await interaction.reply({ + content: `${emojis.no} That hub does not exist.`, + ephemeral: true, + }); + } + } + + // actual code starts here + const alreadyInHub = await networkManager.fetchConnection({ + hubId: hub.id, + serverId: channel.guildId, + }); + + if (alreadyInHub) { + return await interaction.reply({ + content: `${emojis.no} You are already connected to this hub from <#${alreadyInHub.channelId}>.`, + ephemeral: true, + }); + } + + const userBlacklisted = await BlacklistManager.fetchUserBlacklist(hub.id, interaction.user.id); + const serverBlacklisted = await BlacklistManager.fetchUserBlacklist( + hub.id, + interaction.guildId, + ); + + if (userBlacklisted || serverBlacklisted) { + return await interaction.reply({ + content: `${emojis.no} You or this server is blacklisted from this hub.`, + ephemeral: true, + }); + } + + // display onboarding message, also prevents user from joining twice + const onboardingCompleted = await showOnboarding(interaction, hub.name, channel.id); + // if user cancels onboarding or it times out + if (!onboardingCompleted) return await interaction.deleteReply().catch(() => null); + + const webhook = await getOrCreateWebhook(channel); + if (!webhook) return; + + // finally make the connection + await networkManager.createConnection({ + serverId: channel.guildId, + channelId: channel.id, + webhookURL: webhook.url, + hub: { connect: { id: hub.id } }, + connected: true, + compact: false, + profFilter: true, + }); + + await interaction.editReply({ + content: `${emojis.yes} You have successfully connected to **${hub.name}**. Use \`/connection\` to configure your connection.`, + embeds: [], + components: [], + }); + + const totalConnections = await db.connectedList.count({ + where: { hubId: hub.id, connected: true }, + }); + networkManager.sendToNetwork(hub.id, { + content: stripIndents` + A new server has joined us! ${emojis.clipart} + + **Server Name:** __${interaction.guild.name}__ + **Member Count:** __${interaction.guild.memberCount}__ + + We now have **${totalConnections}** servers in the network! + `, + }); + } + + @ComponentInteraction('hub_join') + async handleComponent(interaction: ButtonInteraction) { + if (!interaction.isButton()) return; + + const customId = CustomID.parseCustomId(interaction.customId); + + if (customId.identifier === 'join') { + await interaction.reply('hi'); + } + } +} diff --git a/src/commands/subcommands/hub/manage.ts b/src/commands/subcommands/hub/manage.ts new file mode 100644 index 00000000..ff97cde4 --- /dev/null +++ b/src/commands/subcommands/hub/manage.ts @@ -0,0 +1,364 @@ +import { + ActionRowBuilder, + CacheType, + ChatInputCommandInteraction, + EmbedBuilder, + ModalBuilder, + ModalSubmitInteraction, + StringSelectMenuBuilder, + StringSelectMenuInteraction, + TextInputBuilder, + TextInputStyle, +} from 'discord.js'; +import db from '../../../utils/Db.js'; +import HubCommand from '../../slash/Main/hub.js'; +import { hubs, connectedList } from '@prisma/client'; +import { stripIndents } from 'common-tags'; +import { emojis } from '../../../utils/Constants.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { setComponentExpiry } from '../../../utils/Utils.js'; + +export default class Manage extends HubCommand { + async execute(interaction: ChatInputCommandInteraction) { + // the chosen one heh + const chosenHub = interaction.options.getString('hub', true); + const hubInDb = await db.hubs.findFirst({ + where: { + name: chosenHub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + include: { connections: true }, + }); + + if (!hubInDb) { + await interaction.reply(`${emojis.no} Hub not found.`); + return; + } + + await interaction.reply({ + embeds: [await Manage.hubEmbed(hubInDb)], + components: [Manage.actionsSelect(hubInDb.name, interaction.user.id)], + }); + + // disable components after 5 minutes + setComponentExpiry( + interaction.client.getScheduler(), + await interaction.fetchReply(), + 60 * 5000, + ); + } + + @ComponentInteraction('hub_manage') + async handleComponent(interaction: StringSelectMenuInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + + if (customId.data[0] !== interaction.user.id) { + await interaction.reply({ content: 'You are not allowed to do that!', ephemeral: true }); + return; + } + + const hubInDb = await db.hubs.findFirst({ + where: { name: customId.data[1] }, + include: { connections: true }, + }); + + if (!hubInDb) { + await interaction.reply({ content: 'This hub no longer exists!', ephemeral: true }); + return; + } + + switch (interaction.values[0]) { + case 'icon': { + const modal = new ModalBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_manage_modal', 'icon') + .addData(hubInDb.name) + .toString(), + ) + .setTitle('Change Hub Icon') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Enter Icon URL') + .setPlaceholder('Enter a valid imgur image URL.') + .setStyle(TextInputStyle.Short) + .setCustomId('icon'), + ), + ); + + await interaction.showModal(modal); + break; + } + + case 'description': { + const modal = new ModalBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_manage_modal', 'description') + .addData(hubInDb.name) + .toString(), + ) + .setTitle('Edit Hub Description') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Enter Description') + .setPlaceholder('A detailed description about the hub.') + .setMaxLength(1024) + .setStyle(TextInputStyle.Paragraph) + .setCustomId('description'), + ), + ); + + await interaction.showModal(modal); + break; + } + + case 'banner': { + const modal = new ModalBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_manage_modal', 'banner') + .addData(hubInDb.name) + .toString(), + ) + .setTitle('Set Hub Banner') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Enter Banner URL') + .setPlaceholder('Enter a valid imgur image URL.') + .setStyle(TextInputStyle.Short) + .setCustomId('banner'), + ), + ); + + await interaction.showModal(modal); + break; + } + + case 'visibility': { + const updatedHub = await db.hubs.update({ + where: { id: hubInDb?.id }, + data: { private: !hubInDb?.private }, + include: { connections: true }, + }); + + await interaction.reply({ + content: `Successfully set hub visibility to **${ + updatedHub?.private ? 'Private' : 'Public' + }**.`, + ephemeral: true, + }); + + await interaction.message + .edit({ embeds: [await Manage.hubEmbed(updatedHub)] }) + .catch(() => null); + break; + } + + default: + break; + } + } + + @ComponentInteraction('hub_manage_modal') + async handleModal(interaction: ModalSubmitInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + const hubName = customId.data[0]; + + let hubInDb = await db.hubs.findFirst({ + where: { + name: hubName, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + include: { connections: true }, + }); + + if (!hubInDb) { + await interaction.reply({ + content: + 'This hub no longer exists or you no longer have permissions to perform this action!', + ephemeral: true, + }); + return; + } + + switch (customId.postfix) { + // update description modal + case 'description': { + const description = interaction.fields.getTextInputValue('description'); + await db.hubs.update({ + where: { name: hubName }, + data: { description }, + }); + + await interaction.reply({ + content: 'Successfully updated hub description.', + ephemeral: true, + }); + break; + } + + // change icon modal + case 'icon': { + const newIcon = interaction.fields.getTextInputValue('icon'); + + // check if icon is a valid imgur link + const imgurLink = newIcon.match( + /\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g, + ); + if (!imgurLink) { + await interaction.reply({ + content: 'Invalid icon URL. Please make sure it is a valid imgur image URL.', + ephemeral: true, + }); + return; + } + + await db.hubs.update({ + where: { name: hubName }, + data: { iconUrl: imgurLink[0] }, + }); + + await interaction.reply({ + content: 'Successfully updated icon!', + ephemeral: true, + }); + break; + } + + // change banner modal + case 'banner': { + const newBanner = interaction.fields.getTextInputValue('banner'); + const isImgurUrl = newBanner.match( + /\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g, + ); + + // if banner is not a valid imgur link + if (!isImgurUrl) { + await interaction.reply({ + content: 'Invalid banner URL. Please make sure it is a valid imgur image URL.', + ephemeral: true, + }); + return; + } + + await db.hubs.update({ + where: { name: hubName }, + data: { bannerUrl: isImgurUrl[0] }, + }); + + await interaction.reply({ + content: 'Successfully updated banner!', + ephemeral: true, + }); + break; + } + + default: + break; + } + + // fetch updated data + hubInDb = await db.hubs.findFirst({ + where: { name: hubName }, + include: { connections: true }, + }); + + // update the original message with new embed + if (hubInDb) { + await interaction.message + ?.edit({ embeds: [await Manage.hubEmbed(hubInDb)] }) + .catch(() => null); + } + } + + // utility methods + static actionsSelect(hubName: string, userId: string) { + return new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_manage', 'actions') + .addData(userId) + .addData(hubName) + .toString(), + ) + .addOptions([ + { + label: 'Edit Description', + value: 'description', + description: 'Edit the hub description.', + emoji: '✏️', + }, + { + label: 'Toggle Visibility', + value: 'visibility', + description: 'Toggle the hub visibility between public and private.', + emoji: '🔒', + }, + { + label: 'Set Icon', + value: 'icon', + description: 'Set the hub icon.', + emoji: '🖼️', + }, + { + label: 'Set Banner', + value: 'banner', + description: 'Set the hub banner.', + emoji: '🎨', + }, + ]), + ); + } + + static async hubEmbed(hub: hubs & { connections: connectedList[] }) { + const hubBlacklistedUsers = await db.blacklistedUsers.count({ + where: { hubs: { some: { hubId: hub.id } } }, + }); + const hubBlacklistedServers = await db.blacklistedServers.count({ + where: { hubs: { some: { hubId: hub.id } } }, + }); + return new EmbedBuilder() + .setTitle(hub.name) + .setColor('Random') + .setDescription( + stripIndents` + ${hub.description} + - __**Public:**__ ${hub.private ? emojis.no : emojis.yes} + `, + ) + .setThumbnail(hub.iconUrl) + .setImage(hub.bannerUrl) + .addFields( + { + name: 'Blacklists', + value: stripIndents` + - Users: ${hubBlacklistedUsers} + - Servers: ${hubBlacklistedServers} + `, + inline: true, + }, + + { + name: 'Hub Stats', + value: stripIndents` + - Moderators: ${hub.moderators.length.toString()} + - Connected: ${hub.connections.length} + - Owner: <@${hub.ownerId}> + `, + inline: true, + }, + ); + } +} diff --git a/src/commands/subcommands/hub/settings.ts b/src/commands/subcommands/hub/settings.ts new file mode 100644 index 00000000..a7a4b6bd --- /dev/null +++ b/src/commands/subcommands/hub/settings.ts @@ -0,0 +1,137 @@ +import { + ChatInputCommandInteraction, + CacheType, + ActionRowBuilder, + EmbedBuilder, + StringSelectMenuBuilder, + Snowflake, +} from 'discord.js'; +import db from '../../../utils/Db.js'; +import HubCommand from '../../slash/Main/hub.js'; +import { hubs } from '@prisma/client'; +import { HubSettingsBitField, HubSettingsString } from '../../../utils/BitFields.js'; +import { colors, emojis } from '../../../utils/Constants.js'; +import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { StringSelectMenuInteraction } from 'discord.js'; + +export default class Settings extends HubCommand { + async execute(interaction: ChatInputCommandInteraction): Promise { + const hubName = interaction.options.getString('hub', true); + const hub = await db.hubs.findUnique({ + where: { + name: hubName, + OR: [ + { + moderators: { some: { userId: interaction.user.id, position: 'manager' } }, + }, + { ownerId: interaction.user.id }, + ], + }, + }); + + if (!hub) { + return interaction.reply({ + content: 'Hub not found.', + ephemeral: true, + }); + } + + const hubSettings = new HubSettingsBitField(hub.settings); + const embed = Settings.buildSettingsEmbed(hub); + const selects = Settings.settingsMenu(hubSettings, hubName, interaction.user.id); + + await interaction.reply({ embeds: [embed], components: [selects] }); + } + + @ComponentInteraction('hub_settings') + async handleComponent(interaction: StringSelectMenuInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + const hubName = customId.data[0]; + + // respond to select menu + const selected = interaction.values[0] as HubSettingsString; + + // TODO: implement BlockNSFW, only allow hubs that are explicitly marked as NSFW to have this setting + // & only allow network channels to be marked as NSFW + if (selected === 'BlockNSFW') { + return interaction.reply({ + content: `${emojis.no} This setting cannot be changed yet. Please wait for the next update.`, + ephemeral: true, + }); + } + + let hub = await db.hubs.findFirst({ where: { name: hubName } }); + if (!hub) { + return interaction.reply({ + content: 'Hub not found.', + ephemeral: true, + }); + } + + const hubSettings = new HubSettingsBitField(hub.settings); + hub = await db.hubs.update({ + where: { name: hubName }, + data: { settings: hubSettings.toggle(selected).bitfield }, // toggle the setting + }); + + const embed = Settings.buildSettingsEmbed(hub); + const selects = Settings.settingsMenu(hubSettings, hub.name, interaction.user.id); + + await interaction.update({ + embeds: [embed], + components: [selects], + }); + } + + static buildSettingsEmbed(hub: hubs) { + const settings = new HubSettingsBitField(hub.settings); + const settingDescriptions = { + Reactions: '**Reactions** - Allow users to react to messages.', + HideLinks: '**Hide Links** - Redact links sent by users.', + BlockInvites: '**Block Invites** - Prevent users from sending Discord invites.', + BlockNSFW: '**Block NSFW** - Detect and block NSFW images (static only).', + SpamFilter: '**Spam Filter** - Automatically blacklist spammers for 5 minutes.', + UseNicknames: '**Use Nicknames** - Use server nicknames as the network usernames.', + }; + + return new EmbedBuilder() + .setAuthor({ name: `${hub.name} Settings`, iconURL: hub.iconUrl }) + .setDescription( + Object.entries(settingDescriptions) + .map(([key, value]) => { + const flag = settings.has(key as HubSettingsString); + return `- ${flag ? emojis.enabled : emojis.disabled} ${value}`; + }) + .join('\n'), + ) + .setFooter({ text: 'Use the select menu below to toggle.' }) + .setColor(colors.interchatBlue) + .setTimestamp(); + } + + static settingsMenu(hubSettings: HubSettingsBitField, hubName: string, userId: Snowflake) { + return new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_settings', 'settings') + .addData(hubName) + .addData(userId) + .toString(), + ) + .setPlaceholder('Select an option') + .addOptions( + Object.keys(HubSettingsBitField.Flags).map((key) => { + const flag = hubSettings.has(key as HubSettingsString); + const emoji = flag ? emojis.no : emojis.yes; + return { + label: `${flag ? 'Disable' : 'Enable'} ${key}`, + value: key, + emoji, + }; + }), + ), + ); + } +} diff --git a/src/decorators/Interaction.ts b/src/decorators/Interaction.ts index 3791edad..d91aead2 100644 --- a/src/decorators/Interaction.ts +++ b/src/decorators/Interaction.ts @@ -8,6 +8,8 @@ export function ComponentInteraction(customId: string): MethodDecorator { descriptor: PropertyDescriptor, ) { const originalMethod = descriptor.value; + // NOTE: It is not possible to access other class properties for decorator methods + // so don't try to access `this.` in any decorator method body interactionsMap.set(customId, originalMethod); }; } diff --git a/src/index.ts b/src/index.ts index 17b99543..9405ed21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ import db from './utils/Db.js'; import Logger from './utils/Logger.js'; +import Scheduler from './structures/Scheduler.js'; +import BlacklistManager from './structures/BlacklistManager.js'; import { ClusterManager } from 'discord-hybrid-sharding'; import { updateTopGGStats } from './updater/StatsUpdater.js'; import { isDevBuild } from './utils/Constants.js'; -import { Scheduler } from './structures/Scheduler.js'; import { blacklistedServers, blacklistedUsers } from '@prisma/client'; -import { BlacklistManager } from './structures/BlacklistManager.js'; import { wait } from './utils/Utils.js'; import 'dotenv/config'; diff --git a/src/scripts/network/onboarding.ts b/src/scripts/network/onboarding.ts new file mode 100644 index 00000000..8b03ed8a --- /dev/null +++ b/src/scripts/network/onboarding.ts @@ -0,0 +1,106 @@ +import { stripIndents } from 'common-tags'; +import { + ActionRowBuilder, + ButtonStyle, + ChatInputCommandInteraction, + EmbedBuilder, + ButtonBuilder, + ComponentType, + ButtonInteraction, + AnySelectMenuInteraction, + Collection, +} from 'discord.js'; +import { colors, emojis, rulesEmbed } from '../../utils/Constants.js'; + +const onboardingInProgress = new Collection(); + +/* Make user accept and understand important info on first setup */ +export async function showOnboarding( + interaction: ChatInputCommandInteraction | AnySelectMenuInteraction | ButtonInteraction, + hubName: string, + channelId: string, + ephemeral = false, +) { + // Check if server is already attempting to join a hub + if (onboardingInProgress.has(channelId)) { + const err = { + content: `${emojis.no} There has already been an attempt to join a hub in <#${channelId}>. Please wait for that to finish before trying again!`, + ephemeral, + }; + interaction.deferred || interaction.replied + ? interaction.followUp(err) + : interaction.reply(err); + return; + } + // Mark this as in-progress so server can't join twice + onboardingInProgress.set(channelId, channelId); + + const embed = new EmbedBuilder() + .setTitle(`👋 Hey there, welcome to ${hubName}!`) + .setDescription( + stripIndents` + To keep things organized, it's recommended to use a separate channel for just for this hub. But don't worry, you can always change this later. + + **How it works:** The InterChat Network is like a magic bridge that links channels on different servers that are with us in this hub. Learn more at our [guide](https://discord-interchat.github.io/docs). + `, + ) + .setColor(colors.interchatBlue) + .setFooter({ text: `InterChat Network | Version ${interaction.client.version}` }); + + const nextButton = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId('onboarding_:cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger), + new ButtonBuilder().setCustomId('onboarding_:next').setLabel('Next').setStyle(ButtonStyle.Success), + ); + + const replyMsg = { + embeds: [embed], + components: [nextButton], + fetchReply: true, + ephemeral, + }; + + const reply = await (interaction.deferred + ? interaction.editReply(replyMsg) + : interaction.reply(replyMsg)); + + const filter = (i: ButtonInteraction) => i.user.id === interaction.user.id; + + const response = await reply + .awaitMessageComponent({ + time: 60_000 * 2, + filter, + componentType: ComponentType.Button, + }) + .catch(() => null); + + if (response?.customId === 'onboarding_:next') { + const acceptButton = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId('onboarding_:cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger), + new ButtonBuilder().setCustomId('onboarding_:accept').setLabel('Accept').setStyle(ButtonStyle.Success), + ); + + const acceptOnboarding = await response.update({ + embeds: [rulesEmbed], + components: [acceptButton], + }); + + const acceptResp = await acceptOnboarding + .awaitMessageComponent({ + time: 60_000, + filter, + componentType: ComponentType.Button, + }) + .catch(() => null); + + // To avoid getting interaction failures + await acceptResp?.deferUpdate(); + + // remove in-progress marker as onboarding has either been cancelled or completed + onboardingInProgress.delete(channelId); + + return acceptResp?.customId === 'onboarding_:accept' ? true : false; + } + + onboardingInProgress.delete(channelId); + return false; +} diff --git a/src/structures/BlacklistManager.ts b/src/structures/BlacklistManager.ts index 736c4d16..15866347 100644 --- a/src/structures/BlacklistManager.ts +++ b/src/structures/BlacklistManager.ts @@ -1,13 +1,13 @@ import db from '../utils/Db.js'; import Logger from '../utils/Logger.js'; +import Scheduler from './Scheduler.js'; import SuperClient from '../SuperClient.js'; import { blacklistedServers, blacklistedUsers } from '@prisma/client'; -import { Scheduler } from './Scheduler.js'; import { User, TextBasedChannel, EmbedBuilder } from 'discord.js'; import { emojis, colors } from '../utils/Constants.js'; import { captureException } from '@sentry/node'; -export class BlacklistManager { +export default class BlacklistManager { private scheduler: Scheduler; constructor(scheduler: Scheduler) { diff --git a/src/structures/CommandHandler.ts b/src/structures/CommandHandler.ts deleted file mode 100644 index d3f1606c..00000000 --- a/src/structures/CommandHandler.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Interaction } from 'discord.js'; -import fs from 'fs'; -import path from 'path'; -import Factory from '../Factory.js'; -import { CustomID } from './CustomID.js'; - -const __filename = new URL(import.meta.url).pathname; -const __dirname = path.dirname(__filename); - -export default class CommandHandler extends Factory { - public get commandsMap() { - return this.client.commands; - } - - /** `InteractionCreate` event handler */ - handleInteraction(interaction: Interaction) { - if (interaction.isAutocomplete()) { - const command = this.client.commands.get(interaction.commandName); - if (command?.autocomplete) command.autocomplete(interaction); - } - - else if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { - this.client.commands.get(interaction.commandName)?.execute(interaction); - } - - else { - const customId = CustomID.toJSON(interaction.customId); - const handler = this.client.components.find((_, key) => key.startsWith(customId.identifier)); - if (!handler) { - interaction.reply({ content: 'This button is no longer usable.', ephemeral: true }); - return; - } - handler(interaction); - } - } - - /** - * Loads all commands from the Commands directory - * Commands are automatically added to the `clientCommands` map - */ - static async loadCommandFiles( - commandDir = path.join(__dirname, '..', 'commands'), - ): Promise { - const files = fs.readdirSync(commandDir); - - for (const file of files) { - const filePath = path.join(commandDir, file); - const stats = fs.statSync(filePath); - - if (stats.isDirectory()) { - // If the item is a directory, recursively read its files - await this.loadCommandFiles(filePath); - } - - // If the item is a .js file, read its contents - else if (file.endsWith('.js') && file !== 'Command.js') { - // importing it will automatically add the command to the clientCommands map - const command = await import(filePath); - new command.default().loadCommand(); - } - } - } -} diff --git a/src/structures/CommandManager.ts b/src/structures/CommandManager.ts new file mode 100644 index 00000000..a438efd6 --- /dev/null +++ b/src/structures/CommandManager.ts @@ -0,0 +1,88 @@ +import fs from 'fs'; +import path from 'path'; +import Factory from '../Factory.js'; +import Logger from '../utils/Logger.js'; +import Command from '../commands/Command.js'; +import { emojis } from '../utils/Constants.js'; +import { CustomID } from './CustomID.js'; +import { Interaction } from 'discord.js'; +import { captureException } from '@sentry/node'; + +const __filename = new URL(import.meta.url).pathname; +const __dirname = path.dirname(__filename); + +export default class CommandManager extends Factory { + public get commandsMap() { + return this.client.commands; + } + + /** `InteractionCreate` event handler */ + async handleInteraction(interaction: Interaction) { + try { + if (interaction.isAutocomplete()) { + const command = this.client.commands.get(interaction.commandName); + if (command?.autocomplete) command.autocomplete(interaction); + } + else if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { + this.client.commands.get(interaction.commandName)?.execute(interaction); + } + else { + const customId = CustomID.parseCustomId(interaction.customId); + + // for components have own component collector + const ignoreList = ['page_', 'onboarding_']; + if (ignoreList.includes(customId.identifier)) { + return; + } + + // component decorator stuff + const handler = this.client.components.find((_, key) => + key.startsWith(customId.identifier), + ); + + if (!handler) { + await interaction.reply({ + content: `${emojis.no} This is no longer usable.`, + ephemeral: true, + }); + return; + } + + handler(interaction); + } + } + catch (e) { + Logger.error(e); + captureException(e); + } + } + + /** + * Loads all commands from the Commands directory + * Commands are automatically added to the `clientCommands` map + */ + static async loadCommandFiles( + commandDir = path.join(__dirname, '..', 'commands'), + ): Promise { + const files = fs.readdirSync(commandDir); + + for (const file of files) { + const filePath = path.join(commandDir, file); + const stats = fs.statSync(filePath); + + if (stats.isDirectory() && file !== 'subcommands') { + // If the item is a directory, recursively read its files + await this.loadCommandFiles(filePath); + } + + // If the item is a .js file, read its contents + else if (file.endsWith('.js') && file !== 'Command.js') { + // initializing it will automatically add the command to the clientCommands map + const imported = await import(filePath); + const command = new imported.default() as Command; + command.loadCommand(); + command.loadSubcommands(); + } + } + } +} diff --git a/src/structures/CustomID.ts b/src/structures/CustomID.ts index 4e31febc..db47d028 100644 --- a/src/structures/CustomID.ts +++ b/src/structures/CustomID.ts @@ -15,29 +15,40 @@ export class CustomID { addData(value: string): CustomID { if (!value) return this; + if (value.includes('&')) { + throw new TypeError('Custom ID data cannot contain "&"'); + } + this.customId += `&${value}`; return this; } - static toJSON(customId: string) { - const parsedId = { + static parseCustomId(customId: string) { + const parsed = { identifier: '', postfix: '', + // expiry: undefined as Date | undefined, data: [] as string[], }; for (const [index, part] of customId.split('&').entries()) { if (index === 0) { const [identifier, postfix] = part.split(':'); - parsedId.identifier = identifier; - parsedId.postfix = postfix ?? ''; + parsed.identifier = identifier; + parsed.postfix = postfix; } + // else if (part.startsWith('ex=')) { + // const expiry = parseInt(part.split('=')[1]); + // if (isNaN(expiry)) continue; + + // parsed.expiry = new Date(parseInt(part.split('=')[1])); + // } else { - parsedId.data.push(part); + parsed.data.push(part); } } - return parsedId; + return parsed; } toString() { diff --git a/src/structures/NetworkManager.ts b/src/structures/NetworkManager.ts index 2c7fa588..61137228 100644 --- a/src/structures/NetworkManager.ts +++ b/src/structures/NetworkManager.ts @@ -29,8 +29,18 @@ export interface Networks extends connectedList { } export default class NetworkManager extends Factory { - public async handleNetworkMessage(message: NetworkMessage, network: Networks) { - const settings = new HubSettingsBitField(network.hub?.settings); + public async handleNetworkMessage(message: NetworkMessage) { + const isNetworkMessage = await db.connectedList.findFirst({ + where: { channelId: message.channel.id, connected: true }, + include: { hub: true }, + }); + + // check if the message was sent in a network channel + if (!isNetworkMessage?.hub) return; + + // loop through all connections and send the message + const allConnections = await this.fetchHubNetworks({ hubId: isNetworkMessage.hubId }); + const settings = new HubSettingsBitField(isNetworkMessage.hub.settings); const checksPassed = await this.runChecks(message, settings); if (!checksPassed) return; @@ -52,7 +62,8 @@ export default class NetworkManager extends Factory { const nsfwEmbed = new EmbedBuilder() .setTitle('NSFW Image Detected') - .setDescription(stripIndents` + .setDescription( + stripIndents` I have identified this image as NSFW (Not Safe For Work). Sharing NSFW content is against our network guidelines. Refrain from posting such content here. **NSFW Prediction:** ${predictions[0].className} - ${Math.round(predictions[0].probability * 100)}%`, @@ -77,17 +88,15 @@ export default class NetworkManager extends Factory { : undefined; const referredContent = referenceInDb ? await this.getReferredContent(message) : undefined; - // embeds for the normal mode const { embed, censoredEmbed } = this.buildNetworkEmbed(message, { attachmentURL, referredContent, }); - // loop through all connections and send the message - const allConnections = await this.fetchHubNetworks({ hubId: network.hubId }); const sendResult = allConnections.map(async (connection) => { - try { // parse the webhook url and get the webhook id and token + try { + // parse the webhook url and get the webhook id and token const webhookURL = connection.webhookURL.split('/'); // fetch the webhook from discord const webhook = await this.client.fetchWebhook( @@ -100,42 +109,42 @@ export default class NetworkManager extends Factory { ); // create a jump button to reply button const jumpButton = - reply && referredMessage?.author - ? new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setEmoji(emojis.reply) - .setURL( - `https://discord.com/channels/${connection.serverId}/${reply.channelId}/${reply.messageId}`, - ) - .setLabel( - referredMessage.author.username.length >= 80 - ? '@' + referredMessage.author.username.slice(0, 76) + '...' - : '@' + referredMessage.author.username, - ), - ) - : null; + reply && referredMessage?.author + ? new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.reply) + .setURL( + `https://discord.com/channels/${connection.serverId}/${reply.channelId}/${reply.messageId}`, + ) + .setLabel( + referredMessage.author.username.length >= 80 + ? '@' + referredMessage.author.username.slice(0, 76) + '...' + : '@' + referredMessage.author.username, + ), + ) + : null; // embed format let messageFormat: WebhookMessageCreateOptions = { components: jumpButton ? [jumpButton] : undefined, embeds: [connection.profFilter ? censoredEmbed : embed], files: attachment ? [attachment] : undefined, - username: `${network.hub?.name}`, - avatarURL: network.hub?.iconUrl, + username: `${isNetworkMessage.hub?.name}`, + avatarURL: isNetworkMessage.hub?.iconUrl, threadId: connection.parentId ? connection.channelId : undefined, allowedMentions: { parse: [] }, }; if (connection.compact) { const replyContent = - connection.profFilter && referredContent ? censor(referredContent) : referredContent; + connection.profFilter && referredContent ? censor(referredContent) : referredContent; // preview embed for the message being replied to const replyEmbed = replyContent ? new EmbedBuilder({ description: - replyContent.length > 30 ? replyContent?.slice(0, 30) + '...' : replyContent, + replyContent.length > 30 ? replyContent?.slice(0, 30) + '...' : replyContent, author: { name: `${referredMessage?.author.username.slice(0, 30)}`, icon_url: referredMessage?.author.displayAvatarURL(), @@ -163,14 +172,17 @@ export default class NetworkManager extends Factory { } catch (e) { // return the error and webhook URL to store the message in the db - return { messageOrError: e.message, webhookURL: connection.webhookURL } as NetworkWebhookSendResult; + return { + messageOrError: e.message, + webhookURL: connection.webhookURL, + } as NetworkWebhookSendResult; } }); message.delete().catch(() => null); // store the message in the db - await this.storeMessageData(message, await Promise.all(sendResult), network.hubId); + await this.storeMessageData(message, await Promise.all(sendResult), isNetworkMessage.hubId); } public async runChecks(message: Message, settings: HubSettingsBitField): Promise { @@ -186,8 +198,7 @@ export default class NetworkManager extends Factory { } if ( - settings.has('BlockInvites') && - message.content.includes('discord.gg') || + (settings.has('BlockInvites') && message.content.includes('discord.gg')) || message.content.includes('discord.com/invite') || message.content.includes('dsc.gg') ) { @@ -262,46 +273,35 @@ export default class NetworkManager extends Factory { message: NetworkMessage, opts?: { attachmentURL?: string | null; embedCol?: HexColorString; referredContent?: string }, ): { embed: EmbedBuilder; censoredEmbed: EmbedBuilder } { - const embed = new EmbedBuilder({ - description: message.content, - image: opts?.attachmentURL ? { url: opts?.attachmentURL } : undefined, - author: { + const embed = new EmbedBuilder() + .setAuthor({ name: message.author.username, - icon_url: message.author.displayAvatarURL(), - }, - footer: { + iconURL: message.author.displayAvatarURL(), + }) + .setDescription(message.content) + .addFields( + opts?.referredContent + ? [{ name: 'Replying To:', value: opts.referredContent ?? 'Unknown.' }] + : [], + ) + .setFooter({ text: `From: ${message.guild?.name}`, - icon_url: message.guild?.iconURL() ?? undefined, - }, - fields: opts?.referredContent - ? [{ name: 'Replying To:', value: opts.referredContent ?? 'Unknown.' }] - : undefined, - }).setColor(opts?.embedCol ?? 'Random'); - - const censoredEmbed = EmbedBuilder.from({ - ...embed.toJSON(), - description: message.censoredContent, - fields: opts?.referredContent - ? [{ name: 'Replying To:', value: censor(opts.referredContent) ?? 'Unknown.' }] - : undefined, - }); + iconURL: message.guild?.iconURL() ?? undefined, + }) + .setImage(opts?.attachmentURL ?? null) + .setColor(opts?.embedCol ?? 'Random'); + + const censoredEmbed = EmbedBuilder.from(embed) + .setDescription(message.censoredContent) + .addFields( + opts?.referredContent + ? [{ name: 'Replying To:', value: censor(opts.referredContent) ?? 'Unknown.' }] + : [], + ); return { embed, censoredEmbed }; } - // TODO: Error handlers for these - public async fetchHubNetworks(where: { hubId?: string; hubName?: string }) { - return await db.connectedList.findMany({ where }); - } - - public async fetchConnection(where: Prisma.connectedListWhereUniqueInput) { - return await db.connectedList.findUnique({ where }); - } - - async updateConnection(where: Prisma.connectedListWhereUniqueInput, data: Prisma.connectedListUpdateInput) { - return await db.connectedList.update({ where, data }); - } - /** * Stores message in the db after it has been sent to the network * And disconnects the network if the webhook is invalid @@ -355,4 +355,42 @@ export default class NetworkManager extends Factory { }); } } + + // TODO: Error handlers for these + public async fetchHubNetworks(where: { hubId?: string; hubName?: string }) { + return await db.connectedList.findMany({ where }); + } + + public async fetchConnection(where: Prisma.connectedListWhereInput) { + return await db.connectedList.findFirst({ where }); + } + + async updateConnection( + where: Prisma.connectedListWhereUniqueInput, + data: Prisma.connectedListUpdateInput, + ) { + return await db.connectedList.update({ where, data }); + } + async createConnection(data: Prisma.connectedListCreateInput) { + return await db.connectedList.create({ data }); + } + async sendToNetwork(hubId: string, message: string | WebhookMessageCreateOptions) { + const connections = await this.fetchHubNetworks({ hubId }); + + const res = connections.map(async (connection) => { + try { + const webhookURL = connection.webhookURL.split('/'); + const webhook = await this.client.fetchWebhook( + webhookURL[webhookURL.length - 2], + webhookURL[webhookURL.length - 1], + ); + return webhook.send(message); + } + catch { + return null; + } + }); + + return await Promise.all(res); + } } diff --git a/src/structures/Scheduler.ts b/src/structures/Scheduler.ts index 7a09850a..cf3a7e21 100644 --- a/src/structures/Scheduler.ts +++ b/src/structures/Scheduler.ts @@ -1,4 +1,4 @@ -export class Scheduler { +export default class Scheduler { private tasks: Map void; interval: number; intervalId: NodeJS.Timeout }>; constructor() { diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 0dca41bd..81f91a44 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -1,10 +1,11 @@ import { ClusterClient } from 'discord-hybrid-sharding'; import { Collection, Snowflake } from 'discord.js'; import { Logger } from 'winston'; -import CommandHandler from '../structures/CommandHandler'; +import { Scheduler } from '../structures/Scheduler.ts'; import NSFWClient from '../structures/NSFWDetection.ts'; import NetworkManager from '../structures/NetworkManager.ts'; -import { Scheduler } from '../structures/Scheduler.ts'; +import BlacklistManager from '../structures/BlacklistManager.ts'; +import CommandManager from '../structures/CommandManager.ts'; declare module 'discord.js' { export interface Client { @@ -18,8 +19,8 @@ declare module 'discord.js' { fetchGuild(guildId: Snowflake): Promise; getScheduler(): Scheduler; - getCommandManager(): CommandHandler; - getCommandManager(): CommandHandler; + getCommandManager(): CommandManager; + getCommandManager(): CommandManager; getNetworkManager(): NetworkManager; getBlacklistManager(): BlacklistManager; getNSFWDetector(): NSFWClient; diff --git a/src/updater/ReactionUpdater.ts b/src/updater/ReactionUpdater.ts index 0eaefa33..5f0687c8 100644 --- a/src/updater/ReactionUpdater.ts +++ b/src/updater/ReactionUpdater.ts @@ -3,85 +3,171 @@ import Factory from '../Factory.js'; import { ActionRowBuilder, ButtonBuilder, + ButtonInteraction, ButtonStyle, ComponentType, MessageReaction, PartialMessageReaction, PartialUser, + Snowflake, User, WebhookClient, } from 'discord.js'; -import { - connectedList, - MessageDataChannelAndMessageIds, -} from '@prisma/client'; +import { messageData, MessageDataChannelAndMessageIds } from '@prisma/client'; import { sortReactions } from '../utils/Utils.js'; -import { BlacklistManager } from '../structures/BlacklistManager.js'; import { HubSettingsBitField } from '../utils/BitFields.js'; +import BlacklistManager from '../structures/BlacklistManager.js'; +import { CustomID } from '../structures/CustomID.js'; +import { ComponentInteraction } from '../decorators/Interaction.js'; +import { emojis } from '../utils/Constants.js'; + +type messageAndHubSettings = messageData & { hub: { settings: number } | null }; export default class ReactionUpdater extends Factory { - public async listen( + public async listenForReactions( reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser, ): Promise { - if (user.bot || user.system) return; + if (user.bot) return; + + const cooldown = reaction.client.reactionCooldowns.get(user.id); + if (cooldown && cooldown > Date.now()) return; + + // add user to cooldown list + user.client.reactionCooldowns.set(user.id, Date.now() + 3000); const messageInDb = await db.messageData.findFirst({ where: { channelAndMessageIds: { some: { messageId: reaction.message.id } } }, include: { hub: { select: { settings: true } } }, }); - if ( - !messageInDb?.hub || - !messageInDb.hubId || - !new HubSettingsBitField(messageInDb.hub.settings).has('Reactions') || - !reaction.message.inGuild() - ) { + if (!messageInDb || !reaction.message.inGuild() || !ReactionUpdater.runChecks(messageInDb)) { return; } - const userBlacklisted = await BlacklistManager.fetchUserBlacklist(messageInDb.hubId, user.id); - const serverBlacklisted = await BlacklistManager.fetchUserBlacklist( - messageInDb.hubId, - reaction.message.guild.id, + const { userBlacklisted, serverBlacklisted } = await ReactionUpdater.checkBlacklists( + messageInDb, + reaction.message.guildId, + user.id, ); - if (userBlacklisted || serverBlacklisted) return; - const connections = await db.connectedList.findMany({ - where: { - channelId: { in: messageInDb.channelAndMessageIds.map((c) => c.channelId) }, - connected: true, - }, - }); + if (userBlacklisted || serverBlacklisted) return; const reactedEmoji = reaction.emoji.toString(); - const reactions = messageInDb.reactions?.valueOf() as { [key: string]: string[] }; // eg. { '👍': 1, '👎': 2 } + const dbReactions = messageInDb.reactions?.valueOf() as { [key: string]: string[] }; // eg. { '👍': 1, '👎': 2 } + const emojiAlreadyReacted = dbReactions[reactedEmoji] ?? [user.id]; - if ( - (!reactions[reactedEmoji] && Object.keys(reactions).length >= 10) || - reactions[reactedEmoji]?.includes(user.id) - ) { - return; - } + // max 10 reactions + if (Object.keys(dbReactions).length >= 10) return; - reactions[reactedEmoji] - ? reactions[reactedEmoji].push(user.id) - : (reactions[reactedEmoji] = [user.id]); + // if there already are reactions by others + // and the user hasn't reacted yet + !emojiAlreadyReacted?.includes(user.id) + // add user to the array + ? ReactionUpdater.addReaction(dbReactions, user.id, reactedEmoji) + // or update the data with a new arr containing userId + : (dbReactions[reactedEmoji] = emojiAlreadyReacted); await db.messageData.update({ where: { id: messageInDb.id }, - data: { reactions: reactions }, + data: { reactions: dbReactions }, }); reaction.users.remove(user.id).catch(() => null); - this.updateReactions(connections, messageInDb.channelAndMessageIds, reactions); + ReactionUpdater.updateReactions(messageInDb.channelAndMessageIds, dbReactions); } - updateReactions( - connections: connectedList[], + @ComponentInteraction('reaction_') + async listenForReactionButton(interaction: ButtonInteraction) { + await interaction.deferUpdate(); + + const cooldown = interaction.client.reactionCooldowns.get(interaction.user.id); + + const messageInDb = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: interaction.message.id } } }, + include: { + hub: { select: { connections: { where: { connected: true } }, settings: true } }, + }, + }); + + if (!messageInDb || !interaction.inCachedGuild() || !ReactionUpdater.runChecks(messageInDb)) { + return; + } + + const { userBlacklisted, serverBlacklisted } = await ReactionUpdater.checkBlacklists( + messageInDb, + interaction.guildId, + interaction.user.id, + ); + + // add user to cooldown list + interaction.client.reactionCooldowns.set(interaction.user.id, Date.now() + 3000); + + const customId = CustomID.parseCustomId(interaction.customId); + if (customId.postfix === 'view_all') { + /* */ + } + else { + if (cooldown && cooldown > Date.now()) { + return await interaction.followUp({ + content: `A little quick there! You can react again !`, + ephemeral: true, + }); + } + + if (userBlacklisted) { + await interaction.reply({ + content: 'You are blacklisted from this hub.', + ephemeral: true, + }); + return; + } + else if (serverBlacklisted) { + await interaction.reply({ + content: 'This server is blacklisted from this hub.', + ephemeral: true, + }); + return; + } + + const reactedEmoji = customId.postfix; + const dbReactions = messageInDb.reactions?.valueOf() as { [key: string]: Snowflake[] }; + const emojiAlreadyReacted = dbReactions[reactedEmoji]; + + if (!emojiAlreadyReacted) { + return await interaction.followUp({ + content: `${emojis.no} This is no longer reactable.`, + ephemeral: true, + }); + } + emojiAlreadyReacted.includes(interaction.user.id) + ? // If the user already reacted, remove the reaction + ReactionUpdater.removeReaction(dbReactions, interaction.user.id, reactedEmoji) + : // or else add the user to the array + ReactionUpdater.addReaction(dbReactions, interaction.user.id, reactedEmoji); + + await db.messageData.update({ + where: { id: messageInDb.id }, + data: { reactions: dbReactions }, + }); + + // reflect the changes in the message's buttons + await ReactionUpdater.updateReactions(messageInDb.channelAndMessageIds, dbReactions); + } + } + + // making methods static so we can use them in the decorator + static async updateReactions( channelAndMessageIds: MessageDataChannelAndMessageIds[], reactions: { [key: string]: string[] }, - ): void { + ): Promise { + const connections = await db.connectedList.findMany({ + where: { + channelId: { in: channelAndMessageIds.map((c) => c.channelId) }, + connected: true, + }, + }); + // reactions data example: { '👍': ['userId1', 'userId2'], '👎': ['userId1', 'userId2', 'userId3'] } // sortedReactions[0] = array of [emoji, users[]] // sortedReactions[x] = emojiIds @@ -92,7 +178,7 @@ export default class ReactionUpdater extends Factory { const reactionBtn = new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId(`reaction_${mostReaction}`) + .setCustomId(new CustomID().setIdentifier('reaction_', mostReaction).toString()) .setEmoji(mostReaction) .setStyle(ButtonStyle.Secondary) .setLabel(`${reactionCount}`), @@ -105,7 +191,7 @@ export default class ReactionUpdater extends Factory { if (allReactionCount.length > 0) { reactionBtn.addComponents( new ButtonBuilder() - .setCustomId('view_all_reactions') + .setCustomId(new CustomID().setIdentifier('reaction_', 'view_all').toString()) .setStyle(ButtonStyle.Secondary) .setLabel(`+ ${allReactionCount.length}`), ); @@ -129,7 +215,7 @@ export default class ReactionUpdater extends Factory { return component.type === ComponentType.Button && component.style === ButtonStyle.Secondary ? !component.custom_id.startsWith('reaction_') && - component.custom_id !== 'view_all_reactions' + component.custom_id !== 'reaction_:view_all' : true; }); @@ -148,4 +234,44 @@ export default class ReactionUpdater extends Factory { .catch(() => null); }); } + static runChecks(messageInDb: messageAndHubSettings) { + if ( + !messageInDb.hub || + !messageInDb.hubId || + !new HubSettingsBitField(messageInDb.hub.settings).has('Reactions') + ) { + return false; + } + + return true; + } + + static async checkBlacklists( + messageInDb: messageAndHubSettings | null, + guildId: string, + userId: string, + ) { + if (!messageInDb?.hubId) return { userBlacklisted: false, serverBlacklisted: false }; + + const userBlacklisted = await BlacklistManager.fetchUserBlacklist(messageInDb.hubId, userId); + const guildBlacklisted = await BlacklistManager.fetchUserBlacklist(messageInDb.hubId, guildId); + if (userBlacklisted || guildBlacklisted) { + return { userBlacklisted, serverBlacklisted: guildBlacklisted }; + } + + return { userBlacklisted: false, serverBlacklisted: false }; + } + + static addReaction(reactionArr: { [key: string]: Snowflake[] }, userId: string, emoji: string) { + reactionArr[emoji].push(userId); + } + static removeReaction( + reactionArr: { [key: string]: Snowflake[] }, + userId: string, + emoji: string, + ) { + const userIndex = reactionArr[emoji].indexOf(userId); + reactionArr[emoji].splice(userIndex, 1); + return reactionArr; + } } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index c1a8c30e..43761402 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -23,12 +23,13 @@ export const REGEX = { STATIC_IMAGE_URL: /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.png)/, }; -export const StaffIds = ['597265261665714186', '442653948630007808', '689082827979227160'] as const; +export const StaffIds = ['597265261665714186', '442653948630007808', '689082827979227160']; export const DeveloperIds = [ '828492978716409856', '701727675311587358', '456961943505338369', -] as const; +]; +export const SupporterIds = ['880978672037802014']; export const URLs = { @@ -87,7 +88,7 @@ export const colors = { export const rulesEmbed = new EmbedBuilder() .setColor(colors.interchatBlue) - .setImage('https://i.imgur.com/D2pYagc.png').setDescription(stripIndents` + .setImage('https://i.imgur.com/MBG0Rks.png').setDescription(stripIndents` ### 📜 InterChat Network Rules 1. **Use Common Sense:** Be considerate of others and their views. No slurs, derogatory language or any actions that can disrupt the chat's comfort. diff --git a/src/utils/Pagination.ts b/src/utils/Pagination.ts index bc7d3071..bbd81904 100644 --- a/src/utils/Pagination.ts +++ b/src/utils/Pagination.ts @@ -43,17 +43,17 @@ export async function paginate( const row = new ActionRowBuilder().addComponents([ new ButtonBuilder() .setEmoji(emojiBack) - .setCustomId('1') + .setCustomId('page_:back') .setStyle(ButtonStyle.Primary) .setDisabled(true), new ButtonBuilder() .setEmoji(emojiExit) - .setCustomId('3') + .setCustomId('page_:exit') .setStyle(ButtonStyle.Danger) .setLabel(`Page ${index + 1} of ${pages.length}`), new ButtonBuilder() .setEmoji(emojiNext) - .setCustomId('2') + .setCustomId('page_:next') .setStyle(ButtonStyle.Primary) .setDisabled(pages.length <= index + 1), ]); @@ -77,13 +77,13 @@ export async function paginate( }); col.on('collect', (i) => { - if (i.customId === '1') { + if (i.customId === 'page_:back') { index--; } - else if (i.customId === '2') { + else if (i.customId === 'page_:exit') { index++; } - else if (i.customId === '3') { + else if (i.customId === 'page_:next') { col.stop(); return; } diff --git a/src/utils/RegisterCommands.ts b/src/utils/RegisterCommands.ts index 3d09df26..05dad081 100644 --- a/src/utils/RegisterCommands.ts +++ b/src/utils/RegisterCommands.ts @@ -1,5 +1,5 @@ import Logger from './Logger.js'; -import CommandHandler from '../structures/CommandHandler.js'; +import CommandManager from '../structures/CommandManager.js'; import { REST, Routes } from 'discord.js'; import { CLIENT_ID, SUPPORT_SERVER_ID } from './Constants.js'; import { commandsMap } from '../commands/Command.js'; @@ -7,7 +7,7 @@ import 'dotenv/config'; export default async function registerAllCommands(staffOnly = false) { // make sure CommandsMap is not empty - await CommandHandler.loadCommandFiles(); + await CommandManager.loadCommandFiles(); const rest = new REST().setToken(process.env.TOKEN as string); diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index b5d84dd0..8951a358 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,5 +1,18 @@ -import { Snowflake } from 'discord.js'; -import { URLs } from './Constants.js'; +import { + ActionRow, + ButtonStyle, + ChannelType, + ComponentType, + Message, + MessageActionRowComponent, + NewsChannel, + Snowflake, + TextChannel, + ThreadChannel, +} from 'discord.js'; +import { DeveloperIds, StaffIds, SupporterIds, URLs } from './Constants.js'; +import Scheduler from '../structures/Scheduler.js'; +import { randomBytes } from 'crypto'; /** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ export function msToReadable(milliseconds: number): string { @@ -27,9 +40,7 @@ export function wait(ms: number): Promise { /** Sort the array based on the reaction counts */ export function sortReactions(reactions: { [key: string]: string[] }): [string, string[]][] { // before: { '👍': ['10201930193'], '👎': ['10201930193'] } - return Object - .entries(reactions) - .sort((a, b) => b[1].length - a[1].length); // => [ [ '👎', ['10201930193'] ], [ '👍', ['10201930193'] ] ] + return Object.entries(reactions).sort((a, b) => b[1].length - a[1].length); // => [ [ '👎', ['10201930193'] ], [ '👍', ['10201930193'] ] ] } export async function hasVoted(userId: Snowflake): Promise { @@ -49,4 +60,85 @@ export async function hasVoted(userId: Snowflake): Promise { export function yesOrNoEmoji(option: unknown, yesEmoji: string, noEmoji: string) { return option ? yesEmoji : noEmoji; -} \ No newline at end of file +} + +export function disableComponents(message: Message) { + return message.components.flatMap((row) => { + const jsonRow = row.toJSON(); + jsonRow.components.forEach((component) => (component.disabled = true)); + return jsonRow; + }); +} + +export async function getOrCreateWebhook( + channel: NewsChannel | TextChannel | ThreadChannel, + avatar = 'https://i.imgur.com/80nqtSg.png', +) { + const channelOrParent = + channel.type === ChannelType.GuildText || channel.type == ChannelType.GuildAnnouncement + ? channel + : channel.parent; + + const webhooks = await channelOrParent?.fetchWebhooks(); + const existingWebhook = webhooks?.find((w) => w.owner?.id === channel.client.user?.id); + + if (existingWebhook) { + return existingWebhook; + } + + return await channelOrParent?.createWebhook({ + name: 'InterChat Network', + avatar, + }); +} + +export function getCredits() { + return [...DeveloperIds, ...StaffIds, ...SupporterIds]; +} + +export function checkIfStaff(userId: string, onlyCheckForDev = false) { + const staffMembers = [...DeveloperIds, ...(onlyCheckForDev ? [] : StaffIds)]; + + if (staffMembers.includes(userId)) return true; + return false; +} + +/** + * + * @param scheduler The scheduler to use + * @param message The message on which to disable components + * @param time The time in milliseconds after which to disable the components + */ +export function setComponentExpiry( + scheduler: Scheduler, + message: Message, + time: number | Date, +): string { + const timerId = randomBytes(8).toString('hex'); + scheduler.addTask(`disableComponents_${timerId}`, time, async () => { + const updatedMsg = await message.fetch().catch(() => null); + if (updatedMsg?.components.length === 0 || !updatedMsg?.editable) return; + + const disabled = disableAllComponents(message.components); + await updatedMsg.edit({ components: disabled }); + }); + + return timerId; +} + +export function disableAllComponents( + components: ActionRow[], + disableLinks = false, +) { + return components.map((row) => { + const jsonRow = row.toJSON(); + jsonRow.components.forEach((component) => { + !disableLinks && + (component.type === ComponentType.Button && + component.style === ButtonStyle.Link) + ? (component.disabled = false) // leave link buttons enabled + : (component.disabled = true); + }); + return jsonRow; + }); +} From 135d2bf3525eb82b9dc40460babee2fcfa965593 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:40:16 +0530 Subject: [PATCH 03/13] add more commands and stuff --- package-lock.json | 10155 +++++++++++------ package.json | 12 +- src/SuperClient.ts | 36 +- src/commands/{Command.ts => BaseCommand.ts} | 37 +- src/commands/context-menu/blacklist.ts | 232 + src/commands/context-menu/deleteMsg.ts | 60 + src/commands/context-menu/editMsg.ts | 166 + src/commands/context-menu/messageInfo.ts | 323 + src/commands/context-menu/translate.ts | 148 + src/commands/slash/Information/help.ts | 14 +- src/commands/slash/Information/invite.ts | 4 +- src/commands/slash/Information/rules.ts | 4 +- src/commands/slash/Information/stats.ts | 6 +- src/commands/slash/Information/vote.ts | 4 +- src/commands/slash/Main/blacklist.ts | 261 + src/commands/slash/Main/connection.ts | 43 +- src/commands/slash/Main/hub.ts | 50 +- src/commands/slash/Staff/purge.ts | 254 + src/commands/slash/Support/support.ts | 51 + src/commands/subcommands/blacklist/list.ts | 103 + src/commands/subcommands/blacklist/server.ts | 114 + src/commands/subcommands/blacklist/user.ts | 99 + src/commands/subcommands/hub/browse.ts | 372 + src/commands/subcommands/hub/connections.ts | 79 + src/commands/subcommands/hub/create.ts | 166 + src/commands/subcommands/hub/delete.ts | 101 +- src/commands/subcommands/hub/join.ts | 33 +- src/commands/subcommands/hub/leave.ts | 73 + src/commands/subcommands/hub/manage.ts | 37 +- src/commands/subcommands/hub/settings.ts | 19 +- src/commands/subcommands/support/report.ts | 277 + src/commands/subcommands/support/server.ts | 14 + src/decorators/Interaction.ts | 14 +- src/index.ts | 6 +- src/scripts/network/components.ts | 4 +- src/structures/BlacklistManager.ts | 11 +- src/structures/CommandManager.ts | 29 +- src/structures/CustomID.ts | 86 +- src/structures/NetworkManager.ts | 175 +- src/structures/Scheduler.ts | 26 +- src/typings/index.d.ts | 8 +- src/updater/ReactionUpdater.ts | 127 +- src/utils/Constants.ts | 10 +- src/utils/Pagination.ts | 35 +- src/utils/Profanity.ts | 4 +- src/utils/RegisterCommands.ts | 2 +- src/utils/Translator.cts | 10 + src/utils/Utils.ts | 49 +- tsconfig.json | 2 +- 49 files changed, 9961 insertions(+), 3984 deletions(-) rename src/commands/{Command.ts => BaseCommand.ts} (56%) create mode 100644 src/commands/context-menu/blacklist.ts create mode 100644 src/commands/context-menu/deleteMsg.ts create mode 100644 src/commands/context-menu/editMsg.ts create mode 100644 src/commands/context-menu/messageInfo.ts create mode 100644 src/commands/context-menu/translate.ts create mode 100644 src/commands/slash/Main/blacklist.ts create mode 100644 src/commands/slash/Staff/purge.ts create mode 100644 src/commands/slash/Support/support.ts create mode 100644 src/commands/subcommands/blacklist/list.ts create mode 100644 src/commands/subcommands/blacklist/server.ts create mode 100644 src/commands/subcommands/blacklist/user.ts create mode 100644 src/commands/subcommands/hub/browse.ts create mode 100644 src/commands/subcommands/hub/connections.ts create mode 100644 src/commands/subcommands/hub/create.ts create mode 100644 src/commands/subcommands/hub/leave.ts create mode 100644 src/commands/subcommands/support/report.ts create mode 100644 src/commands/subcommands/support/server.ts create mode 100644 src/utils/Translator.cts diff --git a/package-lock.json b/package-lock.json index 215386c9..bbaab4a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,33 +10,34 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@prisma/client": "^5.4.2", - "@sentry/node": "^7.73.0", - "@tensorflow/tfjs-node": "^4.11.0", + "@sentry/node": "^7.74.0", "@translate-tools/core": "^1.0.0", "common-tags": "^1.8.2", - "discord-arts": "^0.5.7", + "discord-arts": "^0.5.8", "discord-hybrid-sharding": "^2.1.3", "discord.js": "^14.13.0", "dotenv": "^16.3.1", "lodash": "^4.17.21", - "nsfwjs": "^2.4.2", "parse-duration": "^1.1.0", "winston": "^3.11.0" }, "devDependencies": { "@sentry/cli": "^2.21.2", "@types/common-tags": "^1.8.2", + "@types/jest": "^29.5.6", "@types/lodash": "^4.14.199", - "@types/node": "^20.8.4", + "@types/node": "^20.8.6", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.51.0", "husky": "^8.0.3", - "lint-staged": "^14.0.1", + "jest": "^29.7.0", + "lint-staged": "^15.0.1", "prettier": "^3.0.3", "prisma": "^5.4.2", "standard-version": "^9.5.0", + "ts-jest": "^29.1.1", "tsc-watch": "^6.0.4", "typescript": "^5.2.2" }, @@ -53,6 +54,19 @@ "node": ">=0.10.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -66,1310 +80,1130 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "node_modules/@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "engines": { - "node": ">=0.1.90" + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@commitlint/config-validator": { - "version": "17.6.7", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.6.7.tgz", - "integrity": "sha512-vJSncmnzwMvpr3lIcm0I8YVVDJTzyjy7NZAeXbTXy+MPUdAr9pKyyg7Tx/ebOQ9kqzE6O9WT6jg2164br5UdsQ==", + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, - "optional": true, "dependencies": { - "@commitlint/types": "^17.4.4", - "ajv": "^8.11.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { - "node": ">=v14" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/execute-rule": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.4.0.tgz", - "integrity": "sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA==", + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, - "optional": true, - "engines": { - "node": ">=v14" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@commitlint/load": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.7.2.tgz", - "integrity": "sha512-XA7WTnsjHZ4YH6ZYsrnxgLdXzriwMMq+utZUET6spbOEEIPBCDLdOQXS26P+v3TTO4hUHOEhzUquaBv3jbBixw==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, - "optional": true, "dependencies": { - "@commitlint/config-validator": "^17.6.7", - "@commitlint/execute-rule": "^17.4.0", - "@commitlint/resolve-extends": "^17.6.7", - "@commitlint/types": "^17.4.4", - "@types/node": "20.5.1", - "chalk": "^4.1.0", - "cosmiconfig": "^8.0.0", - "cosmiconfig-typescript-loader": "^4.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0", - "resolve-from": "^5.0.0", - "ts-node": "^10.8.1", - "typescript": "^4.6.4 || ^5.0.0" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=v14" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/load/node_modules/@types/node": { - "version": "20.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", - "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "optional": true + "dependencies": { + "yallist": "^3.0.2" + } }, - "node_modules/@commitlint/load/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==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "optional": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@commitlint/load/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "optional": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/load/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==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, - "optional": true, "dependencies": { - "color-name": "~1.1.4" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/load/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, - "optional": true - }, - "node_modules/@commitlint/load/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==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, - "optional": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/load/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==", + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, - "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "@babel/types": "^7.22.15" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/resolve-extends": { - "version": "17.6.7", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.6.7.tgz", - "integrity": "sha512-PfeoAwLHtbOaC9bGn/FADN156CqkFz6ZKiVDMjuC2N5N0740Ke56rKU7Wxdwya8R8xzLK9vZzHgNbuGhaOVKIg==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, - "optional": true, "dependencies": { - "@commitlint/config-validator": "^17.6.7", - "@commitlint/types": "^17.4.4", - "import-fresh": "^3.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { - "node": ">=v14" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@commitlint/types": { - "version": "17.4.4", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.4.4.tgz", - "integrity": "sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, - "optional": true, - "dependencies": { - "chalk": "^4.1.0" - }, "engines": { - "node": ">=v14" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/types/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==", + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, - "optional": true, "dependencies": { - "color-convert": "^2.0.1" + "@babel/types": "^7.22.5" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, - "optional": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/types": "^7.22.5" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/types/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==", + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/types/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==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, - "optional": true + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@commitlint/types/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==", + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, - "optional": true, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/@commitlint/types/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==", + "node_modules/@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, - "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, - "optional": true, "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@discordjs/builders": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.5.tgz", - "integrity": "sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==", - "dependencies": { - "@discordjs/formatters": "^0.3.2", - "@discordjs/util": "^1.0.1", - "@sapphire/shapeshift": "^3.9.2", - "discord-api-types": "0.37.50", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.3", - "tslib": "^2.6.1" + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/@discordjs/collection": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", - "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", - "engines": { - "node": ">=16.11.0" + "node": ">=6.0.0" } }, - "node_modules/@discordjs/formatters": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.2.tgz", - "integrity": "sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "dependencies": { - "discord-api-types": "0.37.50" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=16.11.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@discordjs/rest": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.0.1.tgz", - "integrity": "sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "dependencies": { - "@discordjs/collection": "^1.5.3", - "@discordjs/util": "^1.0.1", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.5.1", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.50", - "magic-bytes.js": "^1.0.15", - "tslib": "^2.6.1", - "undici": "5.22.1" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=16.11.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@discordjs/rest/node_modules/undici": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", - "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "dependencies": { - "busboy": "^1.6.0" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": ">=14.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@discordjs/util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.1.tgz", - "integrity": "sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==", - "engines": { - "node": ">=16.11.0" + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@discordjs/ws": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.1.tgz", - "integrity": "sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "dependencies": { - "@discordjs/collection": "^1.5.3", - "@discordjs/rest": "^2.0.1", - "@discordjs/util": "^1.0.1", - "@sapphire/async-queue": "^1.5.0", - "@types/ws": "^8.5.5", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.50", - "tslib": "^2.6.1", - "ws": "^8.13.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=16.11.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.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" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "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" + "@babel/helper-plugin-utils": "^7.10.4" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/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==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=10.10.0" + "peerDependencies": { + "@babel/core": "^7.0.0-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==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "engines": { - "node": ">=12.22" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", - "dev": true - }, - "node_modules/@hutson/parse-repository-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", - "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, - "optional": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "optional": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, - "optional": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", - "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, "engines": { - "node": ">= 8" + "node": ">=4" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, "dependencies": { - "yallist": "^4.0.0" + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "engines": { - "node": ">=8" + "node": ">=0.1.90" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/@commitlint/config-validator": { + "version": "17.6.7", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-17.6.7.tgz", + "integrity": "sha512-vJSncmnzwMvpr3lIcm0I8YVVDJTzyjy7NZAeXbTXy+MPUdAr9pKyyg7Tx/ebOQ9kqzE6O9WT6jg2164br5UdsQ==", + "dev": true, + "optional": true, "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "@commitlint/types": "^17.4.4", + "ajv": "^8.11.0" }, "engines": { - "node": ">= 8" + "node": ">=v14" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/@commitlint/execute-rule": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-17.4.0.tgz", + "integrity": "sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA==", + "dev": true, + "optional": true, "engines": { - "node": ">=8" + "node": ">=v14" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" + "node_modules/@commitlint/load": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-17.7.2.tgz", + "integrity": "sha512-XA7WTnsjHZ4YH6ZYsrnxgLdXzriwMMq+utZUET6spbOEEIPBCDLdOQXS26P+v3TTO4hUHOEhzUquaBv3jbBixw==", + "dev": true, + "optional": true, + "dependencies": { + "@commitlint/config-validator": "^17.6.7", + "@commitlint/execute-rule": "^17.4.0", + "@commitlint/resolve-extends": "^17.6.7", + "@commitlint/types": "^17.4.4", + "@types/node": "20.5.1", + "chalk": "^4.1.0", + "cosmiconfig": "^8.0.0", + "cosmiconfig-typescript-loader": "^4.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0", + "resolve-from": "^5.0.0", + "ts-node": "^10.8.1", + "typescript": "^4.6.4 || ^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=v14" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "node_modules/@commitlint/load/node_modules/@types/node": { + "version": "20.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", + "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", + "dev": true, + "optional": true + }, + "node_modules/@commitlint/load/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, + "optional": true, "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@napi-rs/canvas": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.44.tgz", - "integrity": "sha512-IyhSndjw29LR1WqkUZvTJI4j8Ve1QGbZYtpdQjJjcFvsvJS4/WHzOWV8ZciLPJBhrYvSQf/JbZJy5LHmFV+plg==", - "engines": { - "node": ">= 10" + "node": ">=8" }, - "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.44", - "@napi-rs/canvas-darwin-arm64": "0.1.44", - "@napi-rs/canvas-darwin-x64": "0.1.44", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.44", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.44", - "@napi-rs/canvas-linux-arm64-musl": "0.1.44", - "@napi-rs/canvas-linux-x64-gnu": "0.1.44", - "@napi-rs/canvas-linux-x64-musl": "0.1.44", - "@napi-rs/canvas-win32-x64-msvc": "0.1.44" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.44.tgz", - "integrity": "sha512-3UDlVf1CnibdUcM0+0xPH4L4/d/tCI895or0y7mr/Xlaa1tDmvcQCvBYl9G54IpXsm+e4T1XkVrGGJD4k1NfSg==", - "cpu": [ - "arm64" - ], + "node_modules/@commitlint/load/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, "optional": true, - "os": [ - "android" - ], + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.44.tgz", - "integrity": "sha512-Y1Yx0H45Iicx2b6pcrlICjlwgylLtqi0t5OJgeUXnxLcJ1+aEpmjLr16tddqHkmGUw/nBRAwfPJrf3GaOwWowQ==", - "cpu": [ - "arm64" - ], + "node_modules/@commitlint/load/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, "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">= 10" + "node": ">=7.0.0" } }, - "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.44.tgz", - "integrity": "sha512-gbzeNz13DFH0Ak5ENyQ5ZEuSuCjNDxA/OV9P5f19lywbOVL5Ol+qgKX0BXBcP3O3IXWahruOvmmLUBn9h1MHpA==", - "cpu": [ - "x64" - ], + "node_modules/@commitlint/load/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, + "optional": true + }, + "node_modules/@commitlint/load/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==", + "dev": true, "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.44.tgz", - "integrity": "sha512-Sad3/eGyzTZiyJFeFrmX1M3aRp0n3qTAXeCm6EeAjCFGk8TWd4cINCGT3IRY4wmCvNnpe6C4fM03K07cU5YYwA==", - "cpu": [ - "arm" - ], + "node_modules/@commitlint/load/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==", + "dev": true, "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.44.tgz", - "integrity": "sha512-bCrI9naYGPRFHePMGN+wlrWzC+Swi6uc1YzFg4/wOYzHKSte8FXHrGspHOPPr12BCEmgg3yXK8nnLjxGdlAWtg==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, + "node_modules/@commitlint/resolve-extends": { + "version": "17.6.7", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-17.6.7.tgz", + "integrity": "sha512-PfeoAwLHtbOaC9bGn/FADN156CqkFz6ZKiVDMjuC2N5N0740Ke56rKU7Wxdwya8R8xzLK9vZzHgNbuGhaOVKIg==", + "dev": true, "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@commitlint/config-validator": "^17.6.7", + "@commitlint/types": "^17.4.4", + "import-fresh": "^3.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=v14" } }, - "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.44.tgz", - "integrity": "sha512-gB/ao9zBQaOJik4arOKJisZaG+v7DuyBW7UdG+0L80msAuJTTH2UgWOnmXfZwPxzxNbFKzOa8r48uVzfTaAHGQ==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, + "node_modules/@commitlint/types": { + "version": "17.4.4", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.4.4.tgz", + "integrity": "sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==", + "dev": true, "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "chalk": "^4.1.0" + }, "engines": { - "node": ">= 10" + "node": ">=v14" } }, - "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.44.tgz", - "integrity": "sha512-pvHy1bJ0DDD4Bsx6yuFnqpIyBW7+2iIK5BpvmL36zXE+7w2MEeaYzLUWTBhrXj8rzHys6MwLmHNlkw65R80YbQ==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, + "node_modules/@commitlint/types/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, "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.44.tgz", - "integrity": "sha512-5QaeYqNZ/u1QI2E/UqvnmuORT6cI1qTtLosPp/y4awaK+/LXQEzotHNv0nan0z4EV/0mXsJswY9JpISRJzx+Kw==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, + "node_modules/@commitlint/types/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, "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.44", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.44.tgz", - "integrity": "sha512-pbeTGLox+I+sMVl/FFO21Xvp0PhijsuEr9gaynmN2X7FPTg+CCuuBDhfSU5iMAtcCCYFCk8ridZIWy5jkcf72w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "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==", + "node_modules/@commitlint/types/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, + "optional": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 8" + "node": ">=7.0.0" } }, - "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==", + "node_modules/@commitlint/types/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, + "optional": true + }, + "node_modules/@commitlint/types/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==", "dev": true, + "optional": true, "engines": { - "node": ">= 8" + "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==", + "node_modules/@commitlint/types/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==", "dev": true, + "optional": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@nsfw-filter/gif-frames": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@nsfw-filter/gif-frames/-/gif-frames-1.0.2.tgz", - "integrity": "sha512-XZrbJWEN8YfVla5i+PD4Wj51rRlJ8OgnXiPjjOt/OsrbsCR9GZRD4jr953oNWcwiRaoIcOCFWQNMQukO7Yb1dA==", - "dependencies": { - "@nsfw-filter/save-pixels": "^2.3.4", - "get-pixels-frame-info-update": "3.3.2", - "multi-integer-range": "3.0.0" - } - }, - "node_modules/@nsfw-filter/save-pixels": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@nsfw-filter/save-pixels/-/save-pixels-2.3.4.tgz", - "integrity": "sha512-dRZXwrXadMvxwJYKChrDBqC6GNvxVqlmdkyvZJO5DV65qyBsHZw8bPg9CnX7EgpxGl6+4ba/MAdHDLxs2XoD0Q==", - "dependencies": { - "gif-encoder": "0.4.1", - "ndarray": "1.0.18", - "ndarray-ops": "1.2.2", - "pngjs-nozlib": "1.0.0", - "through": "2.3.4" + "node": ">=8" } }, - "node_modules/@nsfw-filter/save-pixels/node_modules/through": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.4.tgz", - "integrity": "sha512-DwbmSAcABsMazNkLOJJSLRC3gfh4cPxUxJCn9npmvbcI6undhgoJ2ShvEOgZrW8BH62Gyr9jKboGbfFcmY5VsQ==" - }, - "node_modules/@prisma/client": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.2.tgz", - "integrity": "sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==", - "hasInstallScript": true, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, "dependencies": { - "@prisma/engines-version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@prisma/engines": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.4.2.tgz", - "integrity": "sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==", - "devOptional": true, - "hasInstallScript": true - }, - "node_modules/@prisma/engines-version": { - "version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz", - "integrity": "sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA==" - }, - "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==", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, - "node_modules/@sapphire/shapeshift": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz", - "integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==", + "node_modules/@discordjs/builders": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.5.tgz", + "integrity": "sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==", "dependencies": { + "@discordjs/formatters": "^0.3.2", + "@discordjs/util": "^1.0.1", + "@sapphire/shapeshift": "^3.9.2", + "discord-api-types": "0.37.50", "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" + "ts-mixer": "^6.0.3", + "tslib": "^2.6.1" }, "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=16.11.0" } }, - "node_modules/@sapphire/snowflake": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", - "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=16.11.0" } }, - "node_modules/@sentry-internal/tracing": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.73.0.tgz", - "integrity": "sha512-ig3WL/Nqp8nRQ52P205NaypGKNfIl/G+cIqge9xPW6zfRb5kJdM1YParw9GSJ1SPjEZBkBORGAML0on5H2FILw==", + "node_modules/@discordjs/formatters": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.2.tgz", + "integrity": "sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==", "dependencies": { - "@sentry/core": "7.73.0", - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", - "tslib": "^2.4.1 || ^1.9.3" + "discord-api-types": "0.37.50" }, "engines": { - "node": ">=8" + "node": ">=16.11.0" } }, - "node_modules/@sentry/cli": { - "version": "2.21.2", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.21.2.tgz", - "integrity": "sha512-X1nye89zl+QV3FSuQDGItfM51tW9PQ7ce0TtV/12DgGgTVEgnVp5uvO3wX5XauHvulQzRPzwUL3ZK+yS5bAwCw==", - "dev": true, - "hasInstallScript": true, + "node_modules/@discordjs/rest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.0.1.tgz", + "integrity": "sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==", "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.7", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" - }, - "bin": { - "sentry-cli": "bin/sentry-cli" + "@discordjs/collection": "^1.5.3", + "@discordjs/util": "^1.0.1", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.50", + "magic-bytes.js": "^1.0.15", + "tslib": "^2.6.1", + "undici": "5.22.1" }, "engines": { - "node": ">= 10" + "node": ">=16.11.0" } }, - "node_modules/@sentry/core": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.73.0.tgz", - "integrity": "sha512-9FEz4Gq848LOgVN2OxJGYuQqxv7cIVw69VlAzWHEm3njt8mjvlTq+7UiFsGRo84+59V2FQuHxzA7vVjl90WfSg==", + "node_modules/@discordjs/rest/node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", "dependencies": { - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", - "tslib": "^2.4.1 || ^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/node": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.73.0.tgz", - "integrity": "sha512-i50bRfmgkRRx0XXUbg9jGD/RuznDJxJXc4rBILhoJuhl+BjRIaoXA3ayplfJn8JLZxsNh75uJaCq4IUK70SORw==", - "dependencies": { - "@sentry-internal/tracing": "7.73.0", - "@sentry/core": "7.73.0", - "@sentry/types": "7.73.0", - "@sentry/utils": "7.73.0", - "cookie": "^0.5.0", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^2.4.1 || ^1.9.3" + "busboy": "^1.6.0" }, "engines": { - "node": ">=8" + "node": ">=14.0" } }, - "node_modules/@sentry/types": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.73.0.tgz", - "integrity": "sha512-/v8++bly8jW7r4cP2wswYiiVpn7eLLcqwnfPUMeCQze4zj3F3nTRIKc9BGHzU0V+fhHa3RwRC2ksqTGq1oJMDg==", + "node_modules/@discordjs/util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.1.tgz", + "integrity": "sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==", "engines": { - "node": ">=8" + "node": ">=16.11.0" } }, - "node_modules/@sentry/utils": { - "version": "7.73.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.73.0.tgz", - "integrity": "sha512-h3ZK/qpf4k76FhJV9uiSbvMz3V/0Ovy94C+5/9UgPMVCJXFmVsdw8n/dwANJ7LupVPfYP23xFGgebDMFlK1/2w==", + "node_modules/@discordjs/ws": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.1.tgz", + "integrity": "sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==", "dependencies": { - "@sentry/types": "7.73.0", - "tslib": "^2.4.1 || ^1.9.3" + "@discordjs/collection": "^1.5.3", + "@discordjs/rest": "^2.0.1", + "@discordjs/util": "^1.0.1", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.5", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.50", + "tslib": "^2.6.1", + "ws": "^8.13.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@tensorflow/tfjs": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-3.21.0.tgz", - "integrity": "sha512-khcARd3/872llL/oF4ouR40qlT71mylU66PGT8kHP/GJ5YKj44sv8lDRjU7lOVlJK7jsJFWEsNVHI3eMc/GWNQ==", - "peer": true, - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "3.21.0", - "@tensorflow/tfjs-backend-webgl": "3.21.0", - "@tensorflow/tfjs-converter": "3.21.0", - "@tensorflow/tfjs-core": "3.21.0", - "@tensorflow/tfjs-data": "3.21.0", - "@tensorflow/tfjs-layers": "3.21.0", - "argparse": "^1.0.10", - "chalk": "^4.1.0", - "core-js": "3", - "regenerator-runtime": "^0.13.5", - "yargs": "^16.0.3" - }, - "bin": { - "tfjs-custom-module": "dist/tools/custom_module/cli.js" + "node": ">=16.11.0" } }, - "node_modules/@tensorflow/tfjs-backend-cpu": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz", - "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==", - "peer": true, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "dependencies": { - "@types/seedrandom": "^2.4.28", - "seedrandom": "^3.0.5" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "yarn": ">= 1.3.2" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "@tensorflow/tfjs-core": "3.21.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.21.0.tgz", - "integrity": "sha512-N4zitIAT9IX8B8oe489qM3f3VcESxGZIZvHmVP8varOQakTvTX859aaPo1s8hK1qCy4BjSGbweooZe4U8D4kTQ==", - "peer": true, - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "3.21.0", - "@types/offscreencanvas": "~2019.3.0", - "@types/seedrandom": "^2.4.28", - "@types/webgl-ext": "0.0.30", - "@types/webgl2": "0.0.6", - "seedrandom": "^3.0.5" - }, + "node_modules/@eslint-community/regexpp": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "dev": true, "engines": { - "yarn": ">= 1.3.2" - }, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.21.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@tensorflow/tfjs-converter": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.21.0.tgz", - "integrity": "sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==", - "peer": true, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.21.0" - } - }, - "node_modules/@tensorflow/tfjs-core": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.21.0.tgz", - "integrity": "sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==", - "peer": true, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, "dependencies": { - "@types/long": "^4.0.1", - "@types/offscreencanvas": "~2019.3.0", - "@types/seedrandom": "^2.4.28", - "@types/webgl-ext": "0.0.30", - "@webgpu/types": "0.1.16", - "long": "4.0.0", - "node-fetch": "~2.6.1", - "seedrandom": "^3.0.5" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.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": { - "yarn": ">= 1.3.2" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@tensorflow/tfjs-core/node_modules/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", - "peer": true, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "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/@eslint/eslintrc/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==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "dev": true, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@tensorflow/tfjs-data": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-3.21.0.tgz", - "integrity": "sha512-eFLfw2wIcFNxnP2Iv/SnVlihehzKMumk1b5Prcx1ixk/SbkCo5u0Lt7OVOWaEOKVqvB2sT+dJcTjAh6lrCC/QA==", - "peer": true, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, "dependencies": { - "@types/node-fetch": "^2.1.2", - "node-fetch": "~2.6.1", - "string_decoder": "^1.3.0" + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" }, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.21.0", - "seedrandom": "^3.0.5" + "engines": { + "node": ">=10.10.0" } }, - "node_modules/@tensorflow/tfjs-data/node_modules/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.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==", + "dev": true, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=12.22" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@tensorflow/tfjs-layers": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-3.21.0.tgz", - "integrity": "sha512-CMVXsraakXgnXEnqD9QbtResA7nvV7Jz20pGmjFIodcQkClgmFFhdCG5N+zlVRHEz7VKG2OyfhltZ0dBq/OAhA==", - "peer": true, - "peerDependencies": { - "@tensorflow/tfjs-core": "3.21.0" + "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==", + "dev": true + }, + "node_modules/@hutson/parse-repository-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", + "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@tensorflow/tfjs-node": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.11.0.tgz", - "integrity": "sha512-dKMabHsyXEjVM9hSPITa9s7+SA7mqIRCN3ITbOoiVQ4JVlpSg2sffORWOQaRbYISP7F+l6RFiw0EB7t5vCXPzg==", - "hasInstallScript": true, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, "dependencies": { - "@mapbox/node-pre-gyp": "1.0.9", - "@tensorflow/tfjs": "4.11.0", - "adm-zip": "^0.5.2", - "google-protobuf": "^3.9.2", - "https-proxy-agent": "^2.2.1", - "progress": "^2.0.0", - "rimraf": "^2.6.2", - "tar": "^4.4.6" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=8.11.0" + "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.11.0.tgz", - "integrity": "sha512-s6Vbz3IvMz2zNbH8/VptpRXzkwVjmuzT48esYLXJxMKtTcob4m5Srdxo7B+eJSDrWYkutXruiivaWmihFmu5rA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.11.0", - "@tensorflow/tfjs-backend-webgl": "4.11.0", - "@tensorflow/tfjs-converter": "4.11.0", - "@tensorflow/tfjs-core": "4.11.0", - "@tensorflow/tfjs-data": "4.11.0", - "@tensorflow/tfjs-layers": "4.11.0", - "argparse": "^1.0.10", - "chalk": "^4.1.0", - "core-js": "3.29.1", - "regenerator-runtime": "^0.13.5", - "yargs": "^16.0.3" - }, - "bin": { - "tfjs-custom-module": "dist/tools/custom_module/cli.js" + "sprintf-js": "~1.0.2" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-backend-cpu": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.11.0.tgz", - "integrity": "sha512-2zmGX9MuR8AwscSGOybz4fBOFgQDnj+ZCWGkLxDzbKecy9GxuilukT46xB2zU0kSq7Mf3ncfE/9eUEy6a7ZDqQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "dependencies": { - "@types/seedrandom": "^2.4.28", - "seedrandom": "^3.0.5" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "yarn": ">= 1.3.2" - }, - "peerDependencies": { - "@tensorflow/tfjs-core": "4.11.0" + "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.11.0.tgz", - "integrity": "sha512-sM/B65u+1T3U+Ctiq1fn5j6VmiLEZW7BpuSa3ZXDXtIS07MoZ2FTuO8BMudxEY4xGpTyoOzqTOGT9BaGO3qrWg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.11.0", - "@types/offscreencanvas": "~2019.3.0", - "@types/seedrandom": "^2.4.28", - "seedrandom": "^3.0.5" - }, - "engines": { - "yarn": ">= 1.3.2" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "peerDependencies": { - "@tensorflow/tfjs-core": "4.11.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-converter": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.11.0.tgz", - "integrity": "sha512-j2JEVwkqh+pyin+sxUiNUG7QOIU2S0+5SzN8LFXHlR90/bPvC2qiaaSPYdGG/BYidFc27QCHD3obBXrb1EE/ow==", - "peerDependencies": { - "@tensorflow/tfjs-core": "4.11.0" + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.11.0.tgz", - "integrity": "sha512-t0mCNIco8wg6aZdHWT1d6ZuKtbbdY5y871ELWLSUA1+grXDvvaroHYh5eeJexJYXeg+EQ0/hzB0G8nLsLjlyVQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "dependencies": { - "@types/long": "^4.0.1", - "@types/offscreencanvas": "~2019.7.0", - "@types/seedrandom": "^2.4.28", - "@webgpu/types": "0.1.30", - "long": "4.0.0", - "node-fetch": "~2.6.1", - "seedrandom": "^3.0.5" + "p-try": "^2.0.0" }, "engines": { - "yarn": ">= 1.3.2" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { - "version": "2019.7.1", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.1.tgz", - "integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==" - }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-data": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.11.0.tgz", - "integrity": "sha512-8E6CVpd7kxRFtVL7kvz6WF5jH18pNN2wEcm2yA87xq37JwcRsIPTkrmfyqCHlJZmiWn3RQbP59Sl05gbBnFo5w==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "dependencies": { - "@types/node-fetch": "^2.1.2", - "node-fetch": "~2.6.1", - "string_decoder": "^1.3.0" + "p-limit": "^2.2.0" }, - "peerDependencies": { - "@tensorflow/tfjs-core": "4.11.0", - "seedrandom": "^3.0.5" + "engines": { + "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@tensorflow/tfjs-layers": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.11.0.tgz", - "integrity": "sha512-ErVqwxjpu2YM3uJRj2o5GbBTYViUwnqOb0wKWuCVukVmGeWrUzf1X00Ky3dP4xfilfAvq+B26dg7QN4YNHeaKg==", - "peerDependencies": { - "@tensorflow/tfjs-core": "4.11.0" + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/@webgpu/types": { - "version": "0.1.30", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.30.tgz", - "integrity": "sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==" - }, - "node_modules/@tensorflow/tfjs-node/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, "dependencies": { - "es6-promisify": "^5.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">= 4.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/ansi-styles": { + "node_modules/@jest/console/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, "dependencies": { "color-convert": "^2.0.1" }, @@ -1380,18 +1214,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/chalk": { + "node_modules/@jest/console/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, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1403,10 +1230,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/color-convert": { + "node_modules/@jest/console/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, "dependencies": { "color-name": "~1.1.4" }, @@ -1414,95 +1242,85 @@ "node": ">=7.0.0" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/color-name": { + "node_modules/@jest/console/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==" - }, - "node_modules/@tensorflow/tfjs-node/node_modules/core-js": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", - "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/@tensorflow/tfjs-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "node_modules/@tensorflow/tfjs-node/node_modules/has-flag": { + "node_modules/@jest/console/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==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "node_modules/@jest/console/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==", + "dev": true, "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 4.5.0" + "node": ">=8" } }, - "node_modules/@tensorflow/tfjs-node/node_modules/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "encoding": "^0.1.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "encoding": { + "node-notifier": { "optional": true } } }, - "node_modules/@tensorflow/tfjs-node/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/@tensorflow/tfjs-node/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@tensorflow/tfjs/node_modules/ansi-styles": { + "node_modules/@jest/core/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==", - "peer": true, + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1513,20 +1331,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@tensorflow/tfjs/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@tensorflow/tfjs/node_modules/chalk": { + "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1538,11 +1347,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@tensorflow/tfjs/node_modules/color-convert": { + "node_modules/@jest/core/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==", - "peer": true, + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1550,26 +1359,26 @@ "node": ">=7.0.0" } }, - "node_modules/@tensorflow/tfjs/node_modules/color-name": { + "node_modules/@jest/core/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==", - "peer": true + "dev": true }, - "node_modules/@tensorflow/tfjs/node_modules/has-flag": { + "node_modules/@jest/core/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==", - "peer": true, + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/@tensorflow/tfjs/node_modules/supports-color": { + "node_modules/@jest/core/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==", - "peer": true, + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -1577,1935 +1386,4724 @@ "node": ">=8" } }, - "node_modules/@translate-tools/core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@translate-tools/core/-/core-1.0.0.tgz", - "integrity": "sha512-FmK4f0SZl81uVIslgNABkZYqticpVyWSMf+JwKwSd/1CDdZXRyYN0YacZdMd8Rcg0pF80zaPOkW6FgvUR8e1vw==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "dependencies": { - "@xmldom/xmldom": "^0.8.1", - "axios": "^0.23.0", - "lodash": "^4.17.21", - "query-string": "^6.14.1", - "xpath": "^0.0.32" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@translate-tools/core/node_modules/axios": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", - "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, "dependencies": { - "follow-redirects": "^1.14.4" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "optional": true + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "optional": true + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, - "optional": true + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, - "optional": true + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } }, - "node_modules/@types/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-Z5UcOnlqxFm0tCCTEhkcVOfSihWOMyKypIWHtQNSYh6fMdPUIVpNdLBYutDpDXVHsMUrYzI2IczUHXIzpUovmQ==", - "dev": true + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", - "dev": true + "node_modules/@jest/reporters/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/@types/lodash": { - "version": "4.14.199", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", - "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", - "dev": true + "node_modules/@jest/reporters/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, + "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/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + "node_modules/@jest/reporters/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "node_modules/@types/minimist": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", - "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", + "node_modules/@jest/reporters/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 }, - "node_modules/@types/node": { - "version": "20.8.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", - "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", - "dependencies": { - "undici-types": "~5.25.1" + "node_modules/@jest/reporters/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==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==", + "node_modules/@jest/reporters/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==", + "dev": true, "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", - "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", - "dev": true - }, - "node_modules/@types/offscreencanvas": { - "version": "2019.3.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", - "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/seedrandom": { - "version": "2.4.31", - "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.31.tgz", - "integrity": "sha512-O8t5IyMqJ5qSxOR/UJ4hWL64ix05ofO7FV9IgMwVtUvHu7EsI8YyMJOg7SAWrWhDqizj1oxNZAGgfkCrhk7GTQ==" + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", - "dev": true + "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@types/triple-beam": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", - "integrity": "sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g==" + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/webgl-ext": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", - "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==", - "peer": true + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/webgl2": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", - "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==", - "peer": true + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/ws": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", - "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", + "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, "dependencies": { - "@types/node": "*" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", - "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", + "node_modules/@jest/transform/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, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/type-utils": "6.7.5", - "@typescript-eslint/utils": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@typescript-eslint/parser": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", - "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "node_modules/@jest/transform/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, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", - "debug": "^4.3.4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/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, + "dependencies": { + "color-name": "~1.1.4" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/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 + }, + "node_modules/@jest/transform/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/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, + "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/@jest/types/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/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 + }, + "node_modules/@jest/types/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.44.tgz", + "integrity": "sha512-IyhSndjw29LR1WqkUZvTJI4j8Ve1QGbZYtpdQjJjcFvsvJS4/WHzOWV8ZciLPJBhrYvSQf/JbZJy5LHmFV+plg==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.44", + "@napi-rs/canvas-darwin-arm64": "0.1.44", + "@napi-rs/canvas-darwin-x64": "0.1.44", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.44", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.44", + "@napi-rs/canvas-linux-arm64-musl": "0.1.44", + "@napi-rs/canvas-linux-x64-gnu": "0.1.44", + "@napi-rs/canvas-linux-x64-musl": "0.1.44", + "@napi-rs/canvas-win32-x64-msvc": "0.1.44" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.44.tgz", + "integrity": "sha512-3UDlVf1CnibdUcM0+0xPH4L4/d/tCI895or0y7mr/Xlaa1tDmvcQCvBYl9G54IpXsm+e4T1XkVrGGJD4k1NfSg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.44.tgz", + "integrity": "sha512-Y1Yx0H45Iicx2b6pcrlICjlwgylLtqi0t5OJgeUXnxLcJ1+aEpmjLr16tddqHkmGUw/nBRAwfPJrf3GaOwWowQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.44.tgz", + "integrity": "sha512-gbzeNz13DFH0Ak5ENyQ5ZEuSuCjNDxA/OV9P5f19lywbOVL5Ol+qgKX0BXBcP3O3IXWahruOvmmLUBn9h1MHpA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.44.tgz", + "integrity": "sha512-Sad3/eGyzTZiyJFeFrmX1M3aRp0n3qTAXeCm6EeAjCFGk8TWd4cINCGT3IRY4wmCvNnpe6C4fM03K07cU5YYwA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.44.tgz", + "integrity": "sha512-bCrI9naYGPRFHePMGN+wlrWzC+Swi6uc1YzFg4/wOYzHKSte8FXHrGspHOPPr12BCEmgg3yXK8nnLjxGdlAWtg==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.44.tgz", + "integrity": "sha512-gB/ao9zBQaOJik4arOKJisZaG+v7DuyBW7UdG+0L80msAuJTTH2UgWOnmXfZwPxzxNbFKzOa8r48uVzfTaAHGQ==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.44.tgz", + "integrity": "sha512-pvHy1bJ0DDD4Bsx6yuFnqpIyBW7+2iIK5BpvmL36zXE+7w2MEeaYzLUWTBhrXj8rzHys6MwLmHNlkw65R80YbQ==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.44.tgz", + "integrity": "sha512-5QaeYqNZ/u1QI2E/UqvnmuORT6cI1qTtLosPp/y4awaK+/LXQEzotHNv0nan0z4EV/0mXsJswY9JpISRJzx+Kw==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.44.tgz", + "integrity": "sha512-pbeTGLox+I+sMVl/FFO21Xvp0PhijsuEr9gaynmN2X7FPTg+CCuuBDhfSU5iMAtcCCYFCk8ridZIWy5jkcf72w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@prisma/client": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.4.2.tgz", + "integrity": "sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574" + }, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.4.2.tgz", + "integrity": "sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz", + "integrity": "sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA==" + }, + "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==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz", + "integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==", + "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.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", + "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.74.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.74.0.tgz", + "integrity": "sha512-JK6IRGgdtZjswGfaGIHNWIThffhOHzVIIaGmglui+VFIzOsOqePjoxaDV0MEvzafxXZD7eWqGE5RGuZ0n6HFVg==", + "dependencies": { + "@sentry/core": "7.74.0", + "@sentry/types": "7.74.0", + "@sentry/utils": "7.74.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/cli": { + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.21.2.tgz", + "integrity": "sha512-X1nye89zl+QV3FSuQDGItfM51tW9PQ7ce0TtV/12DgGgTVEgnVp5uvO3wX5XauHvulQzRPzwUL3ZK+yS5bAwCw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@sentry/core": { + "version": "7.74.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.74.0.tgz", + "integrity": "sha512-83NRuqn7nDZkSVBN5yJQqcpXDG4yMYiB7TkYUKrGTzBpRy6KUOrkCdybuKk0oraTIGiGSe5WEwCFySiNgR9FzA==", + "dependencies": { + "@sentry/types": "7.74.0", + "@sentry/utils": "7.74.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.74.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.74.0.tgz", + "integrity": "sha512-uBmW2/z0cz/WFIG74ZF7lSipO0XNzMf9yrdqnZXnGDYsUZE4I4QiqDN0hNi6fkTgf9MYRC8uFem2OkAvyPJ74Q==", + "dependencies": { + "@sentry-internal/tracing": "7.74.0", + "@sentry/core": "7.74.0", + "@sentry/types": "7.74.0", + "@sentry/utils": "7.74.0", + "cookie": "^0.5.0", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.74.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.74.0.tgz", + "integrity": "sha512-rI5eIRbUycWjn6s6o3yAjjWtIvYSxZDdnKv5je2EZINfLKcMPj1dkl6wQd2F4y7gLfD/N6Y0wZYIXC3DUdJQQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.74.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.74.0.tgz", + "integrity": "sha512-k3np8nuTPtx5KDODPtULfFln4UXdE56MZCcF19Jv6Ljxf+YN/Ady1+0Oi3e0XoSvFpWNyWnglauT7M65qCE6kg==", + "dependencies": { + "@sentry/types": "7.74.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@translate-tools/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@translate-tools/core/-/core-1.0.0.tgz", + "integrity": "sha512-FmK4f0SZl81uVIslgNABkZYqticpVyWSMf+JwKwSd/1CDdZXRyYN0YacZdMd8Rcg0pF80zaPOkW6FgvUR8e1vw==", + "dependencies": { + "@xmldom/xmldom": "^0.8.1", + "axios": "^0.23.0", + "lodash": "^4.17.21", + "query-string": "^6.14.1", + "xpath": "^0.0.32" + } + }, + "node_modules/@translate-tools/core/node_modules/axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "optional": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", + "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", + "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", + "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", + "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-Z5UcOnlqxFm0tCCTEhkcVOfSihWOMyKypIWHtQNSYh6fMdPUIVpNdLBYutDpDXVHsMUrYzI2IczUHXIzpUovmQ==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", + "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz", + "integrity": "sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz", + "integrity": "sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", + "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", + "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.8.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", + "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", + "dependencies": { + "undici-types": "~5.25.1" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", + "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", + "dev": true + }, + "node_modules/@types/triple-beam": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", + "integrity": "sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g==" + }, + "node_modules/@types/ws": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", + "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", + "integrity": "sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.2.tgz", + "integrity": "sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", + "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/type-utils": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", + "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", + "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", + "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", + "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", + "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", + "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", + "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.7.5", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz", + "integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "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==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", + "dev": true + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true + }, + "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==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/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, + "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/babel-jest/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/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 + }, + "node_modules/babel-jest/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "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==", + "dev": true, + "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, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "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/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "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/cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001550", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001550.tgz", + "integrity": "sha512-p82WjBYIypO0ukTsd/FG3Xxs+4tFeaY9pfT4amQL8KWtYH7H9nYwReGAbMTJ0hsmRO8IfDtsS6p3ZWj8+1c2RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "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==", + "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==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/commitizen": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.0.tgz", + "integrity": "sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==", + "dev": true, + "dependencies": { + "cachedir": "2.3.0", + "cz-conventional-changelog": "3.3.0", + "dedent": "0.7.0", + "detect-indent": "6.1.0", + "find-node-modules": "^2.1.2", + "find-root": "1.1.0", + "fs-extra": "9.1.0", + "glob": "7.2.3", + "inquirer": "8.2.5", + "is-utf8": "^0.2.1", + "lodash": "4.17.21", + "minimist": "1.2.7", + "strip-bom": "4.0.0", + "strip-json-comments": "3.1.1" + }, + "bin": { + "commitizen": "bin/commitizen", + "cz": "bin/git-cz", + "git-cz": "bin/git-cz" + }, + "engines": { + "node": ">= 12" + } + }, + "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==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.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==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/conventional-changelog": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", + "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^5.0.12", + "conventional-changelog-atom": "^2.0.8", + "conventional-changelog-codemirror": "^2.0.8", + "conventional-changelog-conventionalcommits": "^4.5.0", + "conventional-changelog-core": "^4.2.1", + "conventional-changelog-ember": "^2.0.9", + "conventional-changelog-eslint": "^3.0.9", + "conventional-changelog-express": "^2.0.6", + "conventional-changelog-jquery": "^3.0.11", + "conventional-changelog-jshint": "^2.0.9", + "conventional-changelog-preset-loader": "^2.3.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", + "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-atom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-codemirror": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-config-spec": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", + "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", + "dev": true + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", + "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", + "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", + "dev": true, + "dependencies": { + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^5.0.0", + "conventional-commits-parser": "^3.2.0", + "dateformat": "^3.0.0", + "get-pkg-repo": "^4.0.0", + "git-raw-commits": "^2.0.8", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^4.1.1", + "lodash": "^4.17.15", + "normalize-package-data": "^3.0.0", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "through2": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-ember": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-eslint": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-express": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-jquery": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-jshint": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", + "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/conventional-commit-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", + "integrity": "sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==", + "dev": true + }, + "node_modules/conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-commits-parser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", + "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-recommended-bump": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", + "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", + "dev": true, + "dependencies": { + "concat-stream": "^2.0.0", + "conventional-changelog-preset-loader": "^2.3.4", + "conventional-commits-filter": "^2.0.7", + "conventional-commits-parser": "^3.2.0", + "git-raw-commits": "^2.0.8", + "git-semver-tags": "^4.1.1", + "meow": "^8.0.0", + "q": "^1.5.1" + }, + "bin": { + "conventional-recommended-bump": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "optional": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz", + "integrity": "sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=7", + "ts-node": ">=10", + "typescript": ">=4" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/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, + "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/create-jest/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/create-jest/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 + }, + "node_modules/create-jest/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cz-conventional-changelog": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz", + "integrity": "sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "commitizen": "^4.0.3", + "conventional-commit-types": "^3.0.0", + "lodash.map": "^4.5.1", + "longest": "^2.0.1", + "word-wrap": "^1.0.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@commitlint/load": ">6.1.1" + } + }, + "node_modules/dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.50", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", + "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" + }, + "node_modules/discord-arts": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/discord-arts/-/discord-arts-0.5.8.tgz", + "integrity": "sha512-ekFhBSoBgWytNgZElIAtWtYtludkMlMd+tnFS6Uiov6ae3W5D5CxtRmY9xfsryZ5v2qy65jGQ/FjJcmKGZ8KUA==", + "dependencies": { + "@napi-rs/canvas": "^0.1.33", + "node-fetch": "^2.6.7" + } + }, + "node_modules/discord-hybrid-sharding": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/discord-hybrid-sharding/-/discord-hybrid-sharding-2.1.3.tgz", + "integrity": "sha512-yPBjhvPpV1k+iOGUgohVhtQFpIHuspbq/wFa+VoGDqyujjjsZImDq2TXsBclnThDfMrsSIE47uf2aLMhm8QqBA==", + "dependencies": { + "node-fetch": "^2.6.7" + } + }, + "node_modules/discord.js": { + "version": "14.13.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.13.0.tgz", + "integrity": "sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==", + "dependencies": { + "@discordjs/builders": "^1.6.5", + "@discordjs/collection": "^1.5.3", + "@discordjs/formatters": "^0.3.2", + "@discordjs/rest": "^2.0.1", + "@discordjs/util": "^1.0.1", + "@discordjs/ws": "^1.0.1", + "@sapphire/snowflake": "^3.5.1", + "@types/ws": "^8.5.5", + "discord-api-types": "0.37.50", + "fast-deep-equal": "^3.1.3", + "lodash.snakecase": "^4.1.1", + "tslib": "^2.6.1", + "undici": "5.22.1", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/discord.js/node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotgitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", + "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotgitignore/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dotgitignore/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.559", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz", + "integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "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.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "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", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", - "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5" + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", - "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/utils": "6.7.5", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/types": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", - "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", - "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" + "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": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@typescript-eslint/utils": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", - "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", + "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==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", - "semver": "^7.5.4" + "color-convert": "^2.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", - "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "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==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "eslint-visitor-keys": "^3.4.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz", - "integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@webgpu/types": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz", - "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==", - "peer": true - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "engines": { - "node": ">=10.0.0" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "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==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.4.0" + "node": ">=7.0.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==", + "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==", + "dev": true + }, + "node_modules/eslint/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==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "node_modules/eslint/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==", "dev": true, - "optional": true, "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", + "node_modules/eslint/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==", "dev": true }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "node_modules/eslint/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=6.0" + "node": ">=8" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, "dependencies": { - "debug": "4" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">= 6.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=0.10" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "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==", "dev": true, "dependencies": { - "type-fest": "^0.21.3" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, - "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==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-stream/node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" }, "engines": { - "node": ">=4" + "node": "*" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/execa/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "optional": true - }, - "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==", - "dev": true - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "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==", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, "dependencies": { - "safer-buffer": "~2.1.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, "engines": { - "node": ">=0.8" + "node": ">=4" } }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "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==" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, + "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": ">= 4.0.0" + "node": ">=8.6.0" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "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, + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": "*" + "node": ">= 6" } }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + "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==", + "dev": true }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, - "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==", + "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==", "dev": true, - "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/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dependencies": { - "tweetnacl": "^0.14.3" + "reusify": "^1.0.4" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "bser": "2.1.1" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "escape-string-regexp": "^1.0.5" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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==", "dev": true, - "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/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "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" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=10.16.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/find-node-modules": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.3.tgz", + "integrity": "sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "findup-sync": "^4.0.0", + "merge": "^2.1.1" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "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==", "dev": true, "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + "node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/flat-cache": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=12.0.0" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "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==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/get-pkg-repo": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", + "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", "dev": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@hutson/parse-repository-url": "^3.0.0", + "hosted-git-info": "^4.0.0", + "through2": "^2.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "get-pkg-repo": "src/cli.js" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/get-pkg-repo/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "node_modules/get-pkg-repo/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/get-pkg-repo/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "engines": { - "node": ">= 10" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/get-pkg-repo/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "engines": { - "node": ">=0.8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "node_modules/git-raw-commits": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", + "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "dev": true, "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.js" + }, + "engines": { + "node": ">=10" } }, - "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==", + "node_modules/git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", + "dev": true, "dependencies": { - "color-name": "1.1.3" + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "engines": { + "node": ">=4" } }, - "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==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "node_modules/git-semver-tags": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "dev": true, "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "meow": "^8.0.0", + "semver": "^6.0.0" + }, + "bin": { + "git-semver-tags": "cli.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "node_modules/git-semver-tags/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { - "color-support": "bin.js" + "semver": "bin/semver.js" } }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "node_modules/gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", + "dev": true, "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" + "ini": "^1.3.2" } }, - "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==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { - "delayed-stream": "~1.0.0" + "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": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true, - "engines": { - "node": ">=16" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/commitizen": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.0.tgz", - "integrity": "sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==", + "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==", "dev": true, "dependencies": { - "cachedir": "2.3.0", - "cz-conventional-changelog": "3.3.0", - "dedent": "0.7.0", - "detect-indent": "6.1.0", - "find-node-modules": "^2.1.2", - "find-root": "1.1.0", - "fs-extra": "9.1.0", - "glob": "7.2.3", - "inquirer": "8.2.5", - "is-utf8": "^0.2.1", - "lodash": "4.17.21", - "minimist": "1.2.7", - "strip-bom": "4.0.0", - "strip-json-comments": "3.1.1" - }, - "bin": { - "commitizen": "bin/commitizen", - "cz": "bin/git-cz", - "git-cz": "bin/git-cz" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 12" - } - }, - "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==", - "engines": { - "node": ">=4.0.0" + "node": ">=10.13.0" } }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", "dev": true, + "optional": true, "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" + "ini": "^1.3.4" + }, + "engines": { + "node": ">=4" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, - "engines": [ - "node >= 6.0" - ], "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, - "node_modules/conventional-changelog": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", - "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", "dev": true, "dependencies": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "which": "bin/which" } }, - "node_modules/conventional-changelog-atom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", - "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { - "q": "^1.5.1" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/conventional-changelog-codemirror": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", - "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "q": "^1.5.1" + "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/conventional-changelog-config-spec": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", - "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", - "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=10" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/conventional-changelog-core": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", - "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true, - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/conventional-changelog-ember": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", - "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "dev": true, "dependencies": { - "q": "^1.5.1" + "parse-passwd": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/conventional-changelog-eslint": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", - "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "dependencies": { - "q": "^1.5.1" + "lru-cache": "^6.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/conventional-changelog-express": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", - "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", - "dev": true, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dependencies": { - "q": "^1.5.1" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/conventional-changelog-jquery": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", - "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "dependencies": { - "q": "^1.5.1" + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/conventional-changelog-jshint": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", - "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", + "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==", "dev": true, "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "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/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">= 4" } }, - "node_modules/conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "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==", "dev": true, "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.js" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/conventional-commit-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", - "integrity": "sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==", - "dev": true - }, - "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "node_modules/import-fresh/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==", "dev": true, - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" }, "bin": { - "conventional-commits-parser": "cli.js" + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/conventional-recommended-bump": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", - "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "dependencies": { - "concat-stream": "^2.0.0", - "conventional-changelog-preset-loader": "^2.3.4", - "conventional-commits-filter": "^2.0.7", - "conventional-commits-parser": "^3.2.0", - "git-raw-commits": "^2.0.8", - "git-semver-tags": "^4.1.1", - "meow": "^8.0.0", - "q": "^1.5.1" - }, - "bin": { - "conventional-recommended-bump": "cli.js" - }, "engines": { - "node": ">=10" + "node": ">=0.8.19" } }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "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, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/core-js": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz", - "integrity": "sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==", - "hasInstallScript": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "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==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", "dev": true, - "optional": true, "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=12.0.0" } }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz", - "integrity": "sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==", + "node_modules/inquirer/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, - "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=v14.21.3" + "node": ">=8" }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=7", - "ts-node": ">=10", - "typescript": ">=4" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true - }, - "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==", + "node_modules/inquirer/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, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/cwise-compiler": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz", - "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==", - "dependencies": { - "uniq": "^1.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cz-conventional-changelog": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz", - "integrity": "sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==", + "node_modules/inquirer/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, "dependencies": { - "chalk": "^2.4.1", - "commitizen": "^4.0.3", - "conventional-commit-types": "^3.0.0", - "lodash.map": "^4.5.1", - "longest": "^2.0.1", - "word-wrap": "^1.0.3" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@commitlint/load": ">6.1.1" + "node": ">=7.0.0" } }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "node_modules/inquirer/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 + }, + "node_modules/inquirer/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==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "node_modules/inquirer/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==", + "dev": true, "dependencies": { - "assert-plus": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/data-uri-to-buffer": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz", - "integrity": "sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==" + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "has": "^1.0.3" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "engines": { - "node": ">=0.10" + "node": ">=6" } }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "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==", "dev": true, "dependencies": { - "clone": "^1.0.2" + "is-extglob": "^2.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/delayed-stream": { + "node_modules/is-interactive": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "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, "engines": { - "node": ">=0.10.0" + "node": ">=0.12.0" } }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "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==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, - "optional": true, "engines": { - "node": ">=0.3.1" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "text-extensions": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/discord-api-types": { - "version": "0.37.50", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", - "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" - }, - "node_modules/discord-arts": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/discord-arts/-/discord-arts-0.5.7.tgz", - "integrity": "sha512-/HsFWfoF41EUyJ4uByKRLiJy2FRhXMKOzGw4P/D+E2gt9Sh9des2NRFVSrHAw3k3flwdjU4EN8e2EU+FDHqiTg==", - "dependencies": { - "@napi-rs/canvas": "^0.1.33", - "node-fetch": "^2.6.7" + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/discord-hybrid-sharding": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/discord-hybrid-sharding/-/discord-hybrid-sharding-2.1.3.tgz", - "integrity": "sha512-yPBjhvPpV1k+iOGUgohVhtQFpIHuspbq/wFa+VoGDqyujjjsZImDq2TXsBclnThDfMrsSIE47uf2aLMhm8QqBA==", - "dependencies": { - "node-fetch": "^2.6.7" - } + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true }, - "node_modules/discord.js": { - "version": "14.13.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.13.0.tgz", - "integrity": "sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==", - "dependencies": { - "@discordjs/builders": "^1.6.5", - "@discordjs/collection": "^1.5.3", - "@discordjs/formatters": "^0.3.2", - "@discordjs/rest": "^2.0.1", - "@discordjs/util": "^1.0.1", - "@discordjs/ws": "^1.0.1", - "@sapphire/snowflake": "^3.5.1", - "@types/ws": "^8.5.5", - "discord-api-types": "0.37.50", - "fast-deep-equal": "^3.1.3", - "lodash.snakecase": "^4.1.1", - "tslib": "^2.6.1", - "undici": "5.22.1", - "ws": "^8.13.0" - }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, "engines": { - "node": ">=16.11.0" + "node": ">=0.10.0" } }, - "node_modules/discord.js/node_modules/undici": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", - "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", - "dependencies": { - "busboy": "^1.6.0" - }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, "engines": { - "node": ">=14.0" + "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "is-obj": "^2.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "node_modules/istanbul-lib-report/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==", + "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "node": ">=8" } }, - "node_modules/dotgitignore": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", - "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", + "node_modules/istanbul-lib-report/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==", "dev": true, "dependencies": { - "find-up": "^3.0.0", - "minimatch": "^3.0.4" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/dotgitignore/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "dependencies": { - "locate-path": "^3.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/dotgitignore/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/dotgitignore/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/dotgitignore/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { - "p-limit": "^2.0.0" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dotgitignore/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/jest-changed-files/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dependencies": { - "es6-promise": "^4.0.3" + "node_modules/jest-changed-files/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/jest-changed-files/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/jest-changed-files/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=6" } }, - "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "node_modules/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "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.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "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", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "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.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "path-key": "^3.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/jest-changed-files/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/jest-changed-files/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "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/eslint/node_modules/ansi-styles": { + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/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==", @@ -3520,7 +6118,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/chalk": { + "node_modules/jest-circus/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -3536,7 +6134,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/color-convert": { + "node_modules/jest-circus/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==", @@ -3548,25 +6146,27 @@ "node": ">=7.0.0" } }, - "node_modules/eslint/node_modules/color-name": { + "node_modules/jest-circus/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 }, - "node_modules/eslint/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==", + "node_modules/jest-circus/node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", "dev": true, - "engines": { - "node": ">=10" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/eslint/node_modules/has-flag": { + "node_modules/jest-circus/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==", @@ -3575,13 +6175,7 @@ "node": ">=8" } }, - "node_modules/eslint/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==", - "dev": true - }, - "node_modules/eslint/node_modules/supports-color": { + "node_modules/jest-circus/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==", @@ -3593,1069 +6187,1154 @@ "node": ">=8" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, - "engines": { - "node": ">=0.10" + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/esrecurse": { + "node_modules/jest-cli/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "estraverse": "^5.2.0" + "color-convert": "^2.0.1" }, "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==", - "dev": true, - "engines": { - "node": ">=4.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/jest-cli/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, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/event-stream/node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "node_modules/jest-cli/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, "dependencies": { - "through": "2" + "color-name": "~1.1.4" }, "engines": { - "node": "*" + "node": ">=7.0.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "node_modules/jest-cli/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 }, - "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "node_modules/jest-cli/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==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=8" } }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "node_modules/jest-cli/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==", "dev": true, "dependencies": { - "homedir-polyfill": "^1.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.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==" - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "node_modules/jest-cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "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": ">=12" } }, - "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==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "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==" - }, - "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==", - "dev": true - }, - "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==", + "node_modules/jest-config/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, "dependencies": { - "reusify": "^1.0.4" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "node_modules/jest-config/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, "dependencies": { - "escape-string-regexp": "^1.0.5" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/jest-config/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, "dependencies": { - "flat-cache": "^3.0.4" + "color-name": "~1.1.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=7.0.0" } }, - "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==", + "node_modules/jest-config/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 + }, + "node_modules/jest-config/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==", "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { "node": ">=8" } }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "node_modules/jest-config/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/find-node-modules": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.3.tgz", - "integrity": "sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { - "findup-sync": "^4.0.0", - "merge": "^2.1.1" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true - }, - "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==", + "node_modules/jest-diff/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, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "node_modules/jest-diff/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, "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "node_modules/jest-diff/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, "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "color-name": "~1.1.4" }, "engines": { - "node": ">=12.0.0" + "node": ">=7.0.0" } }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "node_modules/jest-diff/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 }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/jest-diff/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==", + "dev": true, "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=8" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "node_modules/jest-diff/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": "*" + "node": ">=8" } }, - "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==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "node_modules/jest-each/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, "dependencies": { - "minipass": "^2.6.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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==" - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" + "node_modules/jest-each/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, + "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/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/jest-each/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, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=7.0.0" } }, - "node_modules/get-pixels-frame-info-update": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/get-pixels-frame-info-update/-/get-pixels-frame-info-update-3.3.2.tgz", - "integrity": "sha512-LzVij57X/gK4Y6LpcDdqj+R9WCpD6Sv3ZH85GMA+S3xgPGCz81mHql4GiSnF4GijRjk7TE0ja2sDr8FFYKLe2g==", - "dependencies": { - "data-uri-to-buffer": "0.0.3", - "jpeg-js": "^0.3.2", - "mime-types": "^2.0.1", - "ndarray": "^1.0.13", - "ndarray-pack": "^1.1.1", - "node-bitmap": "0.0.1", - "omggif": "^1.0.5", - "parse-data-uri": "^0.2.0", - "pngjs": "^3.3.3", - "request": "^2.44.0", - "through": "^2.3.4" - } + "node_modules/jest-each/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 }, - "node_modules/get-pkg-repo": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", - "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", + "node_modules/jest-each/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==", "dev": true, - "dependencies": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "get-pkg-repo": "src/cli.js" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/get-pkg-repo/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/jest-each/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==", "dev": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/get-pkg-repo/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/get-pkg-repo/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, "dependencies": { - "assert-plus": "^1.0.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gif-encoder": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/gif-encoder/-/gif-encoder-0.4.1.tgz", - "integrity": "sha512-++rNGpDBgWQ9eXj9JfTBLHMUEd7lDOdzIvFyHQM9yL8ffxkcg4G6jWmsgu/r59Uq6nHc3wcVwtgy3geLnIWunQ==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, "dependencies": { - "readable-stream": "~1.1.9" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.8.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gif-encoder/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/gif-encoder/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "node_modules/jest-matcher-utils/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, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/gif-encoder/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "node_modules/jest-matcher-utils/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, "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", + "node_modules/jest-matcher-utils/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, "dependencies": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/git-semver-tags": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", - "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "node_modules/jest-matcher-utils/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 + }, + "node_modules/jest-matcher-utils/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/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==", "dev": true, "dependencies": { - "meow": "^8.0.0", - "semver": "^6.0.0" - }, - "bin": { - "git-semver-tags": "cli.js" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/git-semver-tags/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", + "node_modules/jest-message-util/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, "dependencies": { - "ini": "^1.3.2" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/jest-message-util/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, "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/jest-message-util/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, "dependencies": { - "is-glob": "^4.0.3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10.13.0" + "node": ">=7.0.0" } }, - "node_modules/global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "node_modules/jest-message-util/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 + }, + "node_modules/jest-message-util/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/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==", "dev": true, - "optional": true, "dependencies": { - "ini": "^1.3.4" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, - "bin": { - "which": "bin/which" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "node_modules/jest-resolve/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, "dependencies": { - "type-fest": "^0.20.2" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/jest-resolve/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, "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/google-protobuf": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", - "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/jest-resolve/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, "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": ">=7.0.0" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "node_modules/jest-resolve/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 + }, + "node_modules/jest-resolve/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==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", + "node_modules/jest-resolve/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==", + "dev": true, "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, "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" + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/har-validator/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==" - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "node_modules/jest-runner/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, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "node_modules/jest-runner/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, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">= 0.4.0" + "node": ">=7.0.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/jest-runner/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 + }, + "node_modules/jest-runner/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==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "node_modules/jest-runner/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==", "dev": true, "dependencies": { - "parse-passwd": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "node_modules/jest-runtime/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, "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/jest-runtime/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, "dependencies": { - "agent-base": "6", - "debug": "4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "node_modules/jest-runtime/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, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=14.18.0" + "node": ">=7.0.0" } }, - "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "node_modules/jest-runtime/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 + }, + "node_modules/jest-runtime/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==", "dev": true, - "bin": { - "husky": "lib/bin.js" - }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" + "node": ">=8" } }, - "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==", + "node_modules/jest-runtime/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==", "dev": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "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": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "node_modules/jest-snapshot/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, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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==", + "node_modules/jest-snapshot/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, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/import-fresh/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==", + "node_modules/jest-snapshot/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, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } + "node_modules/jest-snapshot/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 }, - "node_modules/indent-string": { + "node_modules/jest-snapshot/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "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==", + "node_modules/jest-snapshot/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==", + "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=12.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inquirer/node_modules/ansi-styles": { + "node_modules/jest-util/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==", @@ -4670,7 +7349,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/inquirer/node_modules/chalk": { + "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4686,7 +7365,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inquirer/node_modules/color-convert": { + "node_modules/jest-util/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==", @@ -4698,13 +7377,13 @@ "node": ">=7.0.0" } }, - "node_modules/inquirer/node_modules/color-name": { + "node_modules/jest-util/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 }, - "node_modules/inquirer/node_modules/has-flag": { + "node_modules/jest-util/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==", @@ -4713,7 +7392,7 @@ "node": ">=8" } }, - "node_modules/inquirer/node_modules/supports-color": { + "node_modules/jest-util/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==", @@ -4725,190 +7404,233 @@ "node": ">=8" } }, - "node_modules/iota-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", - "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-validate/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, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/jest-validate/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, "dependencies": { - "is-extglob": "^2.1.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "node_modules/jest-validate/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, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "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==", + "node_modules/jest-validate/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 + }, + "node_modules/jest-validate/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==", "dev": true, "engines": { - "node": ">=0.12.0" + "node": ">=8" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/jest-validate/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==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "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==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/jest-watcher/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, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/jest-watcher/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, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "node_modules/jest-watcher/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/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 + }, + "node_modules/jest-watcher/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { - "text-extensions": "^1.0.0" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/jest-worker/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==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "node_modules/jpeg-js": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", - "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4927,10 +7649,17 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } }, "node_modules/json-buffer": { "version": "3.0.1", @@ -4950,11 +7679,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -4971,7 +7695,20 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } }, "node_modules/jsonfile": { "version": "6.1.0", @@ -5010,20 +7747,6 @@ "node": "*" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -5042,11 +7765,29 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5076,27 +7817,27 @@ "dev": true }, "node_modules/lint-staged": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-14.0.1.tgz", - "integrity": "sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.0.1.tgz", + "integrity": "sha512-2IU5OWmCaxch0X0+IBF4/v7sutpB+F3qoXbro43pYjQTOo5wumckjxoxn47pQBqqBsCWrD5HnI2uG/zJA7isew==", "dev": true, "dependencies": { "chalk": "5.3.0", - "commander": "11.0.0", + "commander": "11.1.0", "debug": "4.3.4", - "execa": "7.2.0", + "execa": "8.0.1", "lilconfig": "2.1.0", - "listr2": "6.6.1", + "listr2": "7.0.1", "micromatch": "4.0.5", "pidtree": "0.6.0", "string-argv": "0.3.2", - "yaml": "2.3.1" + "yaml": "2.3.2" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18.12.0" }, "funding": { "url": "https://opencollective.com/lint-staged" @@ -5115,9 +7856,9 @@ } }, "node_modules/listr2": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", - "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-7.0.1.tgz", + "integrity": "sha512-nz+7hwgbDp8eWNoDgzdl4hA/xDSLrNRzPu1TLgOYs6l5Y+Ma6zVWWy9Oyt9TQFONwKoSPoka3H50D3vD5EuNwg==", "dev": true, "dependencies": { "cli-truncate": "^3.1.0", @@ -5129,14 +7870,6 @@ }, "engines": { "node": ">=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } } }, "node_modules/listr2/node_modules/ansi-regex": { @@ -5303,6 +8036,12 @@ "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==", "dev": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5607,11 +8346,6 @@ "triple-beam": "^1.3.0" } }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, "node_modules/longest": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz", @@ -5630,6 +8364,7 @@ "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, "dependencies": { "yallist": "^4.0.0" }, @@ -5643,33 +8378,34 @@ "integrity": "sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==" }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "optional": true + "dependencies": { + "tmpl": "1.0.5" + } }, "node_modules/map-obj": { "version": "4.3.0", @@ -5889,25 +8625,6 @@ "node": ">=8.6" } }, - "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==", - "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==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -5933,6 +8650,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5944,6 +8662,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5962,39 +8681,6 @@ "node": ">= 6" } }, - "node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -6009,11 +8695,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/multi-integer-range": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/multi-integer-range/-/multi-integer-range-3.0.0.tgz", - "integrity": "sha512-uQzynjVJ8F7x5wjaK0g4Ybhy2TvO/pk96+YHyS5g1W4GuUEV6HMebZ8HcRwWgKIRCUT2MLbM5uCKwYcAqkS+8Q==" - }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -6026,46 +8707,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/ndarray": { - "version": "1.0.18", - "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.18.tgz", - "integrity": "sha512-jUz6G+CIsEsqs2VlB1EvaQSAA0Jkf8YKm7eFBleKyhiQjYWzTxXqHzWEOm3jFoGCpxGh4DnPUYHB4ECWE+n9SQ==", - "dependencies": { - "iota-array": "^1.0.0", - "is-buffer": "^1.0.2" - } - }, - "node_modules/ndarray-ops": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz", - "integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==", - "dependencies": { - "cwise-compiler": "^1.0.0" - } - }, - "node_modules/ndarray-pack": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ndarray-pack/-/ndarray-pack-1.2.1.tgz", - "integrity": "sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==", - "dependencies": { - "cwise-compiler": "^1.1.2", - "ndarray": "^1.0.13" - } - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/node-bitmap": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/node-bitmap/-/node-bitmap-0.0.1.tgz", - "integrity": "sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==", - "engines": { - "node": ">=v0.6.5" - } - }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -6091,19 +8738,17 @@ } } }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true }, "node_modules/normalize-package-data": { "version": "3.0.3", @@ -6120,6 +8765,15 @@ "node": ">=10" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -6147,53 +8801,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/nsfwjs": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/nsfwjs/-/nsfwjs-2.4.2.tgz", - "integrity": "sha512-i4Pp2yt59qPQgeZFyg3wXFBX52uSeu/hkDoqdZfe+sILRxNBUu0VDogj7Lmqak0GlrXviS/wLiVeIx40IDUu7A==", - "dependencies": { - "@nsfw-filter/gif-frames": "1.0.2" - }, - "peerDependencies": { - "@tensorflow/tfjs": "^3.18.0" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" - }, "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==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -6391,14 +9003,6 @@ "node": ">=6" } }, - "node_modules/parse-data-uri": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/parse-data-uri/-/parse-data-uri-0.2.0.tgz", - "integrity": "sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==", - "dependencies": { - "data-uri-to-buffer": "0.0.3" - } - }, "node_modules/parse-duration": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", @@ -6444,6 +9048,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -6481,10 +9086,11 @@ "through": "~2.3" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -6519,21 +9125,77 @@ "node": ">=0.10.0" } }, - "node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, "engines": { - "node": ">=4.0.0" + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pngjs-nozlib": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pngjs-nozlib/-/pngjs-nozlib-1.0.0.tgz", - "integrity": "sha512-N1PggqLp9xDqwAoKvGohmZ3m4/N9xpY0nDZivFqQLcpLHmliHnCp9BuNCsOeqHWMuEEgFjpEaq9dZq6RZyy0fA==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "iojs": ">= 1.0.0", - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/prelude-ls": { @@ -6560,6 +9222,32 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prisma": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.4.2.tgz", @@ -6586,10 +9274,24 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, "engines": { "node": ">=0.4.0" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -6611,19 +9313,31 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, "engines": { "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -6634,14 +9348,6 @@ "teleport": ">=0.2.0" } }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/query-string": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", @@ -6688,6 +9394,12 @@ "node": ">=8" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -6856,59 +9568,11 @@ "node": ">=8" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -6940,6 +9604,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -6958,7 +9634,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "optional": true, "engines": { "node": ">=8" } @@ -6976,6 +9651,15 @@ "node": ">=8" } }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -7033,6 +9717,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -7114,17 +9799,14 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/seedrandom": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -7135,11 +9817,6 @@ "node": ">=10" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7164,7 +9841,8 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -7179,6 +9857,12 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -7225,6 +9909,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -7289,31 +9983,8 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, "node_modules/stack-trace": { "version": "0.0.10", @@ -7323,6 +9994,27 @@ "node": "*" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/standard-version": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", @@ -7393,10 +10085,24 @@ "node": ">=0.6.19" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7410,6 +10116,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -7425,6 +10132,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7501,28 +10209,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=4.5" + "node": ">=8" } }, - "node_modules/tar/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -7546,7 +10246,8 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true }, "node_modules/through2": { "version": "4.0.2", @@ -7569,6 +10270,21 @@ "node": ">=0.6.0" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7581,18 +10297,6 @@ "node": ">=8.0" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -7627,6 +10331,58 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/ts-mixer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", @@ -7702,22 +10458,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7730,6 +10470,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -7779,11 +10528,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -7793,10 +10537,41 @@ "node": ">= 10.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -7806,15 +10581,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -7822,6 +10588,30 @@ "dev": true, "optional": true }, + "node_modules/v8-to-istanbul": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -7832,24 +10622,15 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "makeerror": "1.0.12" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -7888,14 +10669,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/winston": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", @@ -7968,6 +10741,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -7984,6 +10758,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -7998,6 +10773,7 @@ "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, "dependencies": { "color-name": "~1.1.4" }, @@ -8008,12 +10784,27 @@ "node_modules/wrap-ansi/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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } }, "node_modules/ws": { "version": "8.14.2", @@ -8056,6 +10847,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" } @@ -8063,12 +10855,13 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", "dev": true, "engines": { "node": ">= 14" @@ -8078,6 +10871,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -8095,6 +10889,7 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, "engines": { "node": ">=10" } diff --git a/package.json b/package.json index a97164ed..48e49d50 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "build/index.js", "license": "AGPL-3.0-or-later", "scripts": { + "test": "node --experimental-vm-modules ./node_modules/.bin/jest", "start": "node .", "build": "tsc --build && npm run sentry:sourcemaps", "build:dev": "tsc --build", @@ -21,10 +22,10 @@ "type": "module", "dependencies": { "@prisma/client": "^5.4.2", - "@sentry/node": "^7.73.0", + "@sentry/node": "^7.74.0", "@translate-tools/core": "^1.0.0", "common-tags": "^1.8.2", - "discord-arts": "^0.5.7", + "discord-arts": "^0.5.8", "discord-hybrid-sharding": "^2.1.3", "discord.js": "^14.13.0", "dotenv": "^16.3.1", @@ -35,17 +36,20 @@ "devDependencies": { "@sentry/cli": "^2.21.2", "@types/common-tags": "^1.8.2", + "@types/jest": "^29.5.6", "@types/lodash": "^4.14.199", - "@types/node": "^20.8.4", + "@types/node": "^20.8.6", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.51.0", "husky": "^8.0.3", - "lint-staged": "^14.0.1", + "jest": "^29.7.0", + "lint-staged": "^15.0.1", "prettier": "^3.0.3", "prisma": "^5.4.2", "standard-version": "^9.5.0", + "ts-jest": "^29.1.1", "tsc-watch": "^6.0.4", "typescript": "^5.2.2" }, diff --git a/src/SuperClient.ts b/src/SuperClient.ts index 8ce52094..f48cd0f8 100644 --- a/src/SuperClient.ts +++ b/src/SuperClient.ts @@ -4,11 +4,11 @@ import { Partials, Options, Collection, - Guild, Snowflake, + Guild, } from 'discord.js'; import { ClusterClient, getInfo } from 'discord-hybrid-sharding'; -import { commandsMap, interactionsMap } from './commands/Command.js'; +import { commandsMap, interactionsMap } from './commands/BaseCommand.js'; import Logger from './utils/Logger.js'; import Scheduler from './structures/Scheduler.js'; import NSFWClient from './structures/NSFWDetection.js'; @@ -16,6 +16,9 @@ import CommandManager from './structures/CommandManager.js'; import NetworkManager from './structures/NetworkManager.js'; import ReactionUpdater from './updater/ReactionUpdater.js'; import BlacklistManager from './structures/BlacklistManager.js'; +import { RemoveMethods } from './typings/index.js'; +import Sentry from '@sentry/node'; +import { isDevBuild } from './utils/Constants.js'; export default abstract class SuperClient extends Client { readonly logger = Logger; @@ -23,7 +26,7 @@ export default abstract class SuperClient extends Client { readonly description = 'The only cross-server communication bot you\'ll ever need.'; readonly version = process.env.npm_package_version ?? 'Unknown'; readonly commands = commandsMap; - readonly components = interactionsMap; + readonly interactions = interactionsMap; readonly commandCooldowns = new Collection(); readonly reactionCooldowns = new Collection(); @@ -47,6 +50,17 @@ export default abstract class SuperClient extends Client { PresenceManager: 0, ReactionManager: 200, }), + sweepers: { + ...Options.DefaultSweeperSettings, + messages: { + interval: 3600, // Every hour... + lifetime: 1800, // Remove messages older than 30 minutes. + }, + reactions: { + interval: 3600, // Every hour... + filter: () => () => true, // Remove all reactions... + }, + }, partials: [Partials.Message], intents: [ IntentsBitField.Flags.MessageContent, @@ -55,12 +69,20 @@ export default abstract class SuperClient extends Client { IntentsBitField.Flags.GuildMessages, IntentsBitField.Flags.GuildMessageReactions, ], - presence: { status: 'invisible' }, }); } protected init() { SuperClient.self = this; + + if (!isDevBuild) { + // error monitoring & handling + Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: this.version, + tracesSampleRate: 1.0, + }); + } } public static getInstance(): SuperClient { @@ -68,10 +90,10 @@ export default abstract class SuperClient extends Client { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - private resolveEval = (value: any[]): T | undefined => value?.find((res) => !!res); + resolveEval = (value: any[]): T | undefined => value?.find((res) => !!res); - async fetchGuild(guildId: Snowflake): Promise { - const fetch = await this.shard?.broadcastEval( + async fetchGuild(guildId: Snowflake): Promise | undefined> { + const fetch = await this.cluster.broadcastEval( (client, guildID) => client.guilds.cache.get(guildID), { context: guildId }, ); diff --git a/src/commands/Command.ts b/src/commands/BaseCommand.ts similarity index 56% rename from src/commands/Command.ts rename to src/commands/BaseCommand.ts index 96b5ddb6..c9002147 100644 --- a/src/commands/Command.ts +++ b/src/commands/BaseCommand.ts @@ -8,31 +8,29 @@ import { RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; import { existsSync, readdirSync } from 'fs'; +import { InteractionFunction } from '../decorators/Interaction.js'; type CommandInteraction = ChatInputCommandInteraction | ContextMenuCommandInteraction; -type InteractionFunction = ( - interaction: MessageComponentInteraction | ModalSubmitInteraction, -) => void; -export const commandsMap = new Collection(); +export const commandsMap = new Collection(); export const interactionsMap = new Collection(); -export default abstract class Command { +export default abstract class BaseCommand { abstract readonly data: RESTPostAPIApplicationCommandsJSONBody; readonly staffOnly?: boolean; readonly description?: string; // wait wtf - static readonly subcommands?: Collection; + static readonly subcommands?: Collection; abstract execute(interaction: CommandInteraction): Promise; // optional methods // eslint-disable-next-line @typescript-eslint/no-unused-vars - handleComponent(interaction: MessageComponentInteraction) { + handleComponents(interaction: MessageComponentInteraction) { /**/ } // eslint-disable-next-line @typescript-eslint/no-unused-vars - handleModal(interaction: ModalSubmitInteraction) { + handleModals(interaction: ModalSubmitInteraction) { /**/ } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -45,18 +43,19 @@ export default abstract class Command { const fullPath = `build/commands/subcommands/${commandName}/`; if (!existsSync(fullPath)) return; - readdirSync(fullPath) - .forEach(async (file) => { - if (file.endsWith('.js')) { - const subcommandFile = (await import(`../commands/subcommands/${commandName}/${file}`)).default; - const subcommandInstance = new subcommandFile() as Command; - const parentCommand = Object.getPrototypeOf(subcommandInstance.constructor); + readdirSync(fullPath).forEach(async (file) => { + if (file.endsWith('.js')) { + const subcommandFile = await import(`../commands/subcommands/${commandName}/${file}`); + const subcommandInstance: BaseCommand = subcommandFile.default + ? new subcommandFile.default() + : new subcommandFile(); + const parentCommand = Object.getPrototypeOf(subcommandInstance.constructor); - // create a new instance of the subcommand class - // and set it in the subcommands map of the parent command - parentCommand.subcommands.set(file.replace('.js', ''), subcommandInstance); - } - }); + // create a new instance of the subcommand class + // and set it in the subcommands map of the parent command + parentCommand.subcommands.set(file.replace('.js', ''), subcommandInstance); + } + }); } /** Save command to the `clientCommands` map */ diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts new file mode 100644 index 00000000..b0ed94e1 --- /dev/null +++ b/src/commands/context-menu/blacklist.ts @@ -0,0 +1,232 @@ +import { + ActionRowBuilder, + ApplicationCommandType, + ButtonBuilder, + ButtonStyle, + CacheType, + EmbedBuilder, + MessageComponentInteraction, + MessageContextMenuCommandInteraction, + ModalBuilder, + ModalSubmitInteraction, + RESTPostAPIApplicationCommandsJSONBody, + TextInputBuilder, + TextInputStyle, +} from 'discord.js'; +import BaseCommand from '../BaseCommand.js'; +import db from '../../utils/Db.js'; +import { emojis } from '../../utils/Constants.js'; +import { CustomID } from '../../structures/CustomID.js'; +import { Interaction } from '../../decorators/Interaction.js'; +import { errorEmbed } from '../../utils/Utils.js'; + +export default class Blacklist extends BaseCommand { + data: RESTPostAPIApplicationCommandsJSONBody = { + type: ApplicationCommandType.Message, + name: 'Blacklist', + dm_permission: false, + }; + + async execute(interaction: MessageContextMenuCommandInteraction) { + const messageInDb = await db.messageData.findFirst({ + where: { + channelAndMessageIds: { some: { messageId: interaction.targetId } }, + hub: { + OR: [ + { moderators: { some: { userId: interaction.user.id } } }, + { ownerId: interaction.user.id }, + ], + }, + }, + }); + + if (!messageInDb) { + interaction.reply({ + embeds: [ + errorEmbed( + 'This message was not sent in a network, has expired or you lack required permissions to perform this action.', + ), + ], + ephemeral: true, + }); + return; + } + + const embed = new EmbedBuilder() + .setTitle('Blacklist') + .setDescription( + 'Blacklist the server or user of this message from this hub. This will prevent messages by them from being sent.', + ) + .setColor('Blurple'); + + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId( + new CustomID() + .setIdentifier('blacklist', 'user') + .addArgs(interaction.user.id) + .addArgs(messageInDb.id) + .addArgs('u=1') + .toString(), + ) + .setLabel('Blacklist User') + .setStyle(ButtonStyle.Secondary) + .setEmoji('👤'), + new ButtonBuilder() + .setCustomId( + new CustomID() + .setIdentifier('blacklist', 'server') + .addArgs(interaction.user.id) + .addArgs(messageInDb.id) + .addArgs('s=1') + .toString(), + ) + .setLabel('Blacklist Server') + .setStyle(ButtonStyle.Secondary) + .setEmoji('🏠'), + ); + + await interaction.reply({ embeds: [embed], components: [buttons] }); + } + + @Interaction('blacklist') + async handleComponents(interaction: MessageComponentInteraction): Promise { + const customId = CustomID.parseCustomId(interaction.customId); + + if (interaction.user.id !== customId.args[0]) { + await interaction.reply({ + embeds: [errorEmbed('This is not your action to perform. Use the command yourself.')], + ephemeral: true, + }); + return; + } + + const messageDocId = customId.args[1]; + const blacklistType = customId.args[2]; + + const modal = new ModalBuilder() + .setTitle('Blacklist') + .setCustomId( + new CustomID() + .setIdentifier('blacklist_modal') + .addArgs(messageDocId) + .addArgs(blacklistType) + .toString(), + ) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('reason') + .setLabel('Reason') + .setPlaceholder('What is the reason for this blacklist?') + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(500), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('duration') + .setLabel('Duration') + .setPlaceholder('Duration of the blacklist. Eg. 1d 2h 3m') + .setStyle(TextInputStyle.Short) + .setMinLength(2) + .setRequired(false), + ), + ); + + await interaction.showModal(modal); + } + + @Interaction('blacklist_modal') + async handleModals(interaction: ModalSubmitInteraction): Promise { + await interaction.deferUpdate(); + + const customId = CustomID.parseCustomId(interaction.customId); + const messageDocId = customId.args[0]; + const blacklistType = customId.args[1]; + + const messageInDb = await db.messageData.findFirst({ + where: { id: messageDocId }, + }); + + if (!messageInDb?.hubId) { + await interaction.reply({ + content: 'This message has expired.', + ephemeral: true, + }); + return; + } + + const reason = interaction.fields.getTextInputValue('reason'); + const duration = parseInt(interaction.fields.getTextInputValue('duration')); + const expires = !isNaN(duration) ? new Date(Date.now() + duration) : undefined; + + const successEmbed = new EmbedBuilder().setColor('Green').addFields( + { + name: 'Reason', + value: reason ? reason : 'No reason provided.', + inline: true, + }, + { + name: 'Expires', + value: expires ? `` : 'Never.', + inline: true, + }, + ); + + const blacklistManager = interaction.client.getBlacklistManager(); + + // user blacklist + if (blacklistType.startsWith('u=')) { + const user = await interaction.client.users.fetch(messageInDb.authorId).catch(() => null); + successEmbed.setDescription( + `${emojis.tick} **${user?.username}** has been successfully blacklisted!`, + ); + await blacklistManager.addUserBlacklist( + messageInDb.hubId, + messageInDb.authorId, + reason, + expires, + ); + + if (expires) { + blacklistManager.scheduleRemoval('user', messageInDb.authorId, messageInDb.hubId, expires); + } + if (user) { + blacklistManager + .notifyBlacklist(user, messageInDb.hubId, expires, reason) + .catch(() => null); + } + + await interaction.editReply({ embeds: [successEmbed], components: [] }); + } + + // server blacklist + else { + successEmbed.setDescription( + `${emojis.tick} **${interaction.client.guilds.cache.get(messageInDb.serverId) + ?.name}** has been successfully blacklisted!`, + ); + await blacklistManager.addServerBlacklist( + messageInDb.serverId, + messageInDb.hubId, + reason, + expires, + ); + await db.connectedList.deleteMany({ + where: { serverId: messageInDb.serverId, hubId: messageInDb.hubId }, + }); + + if (expires) { + blacklistManager.scheduleRemoval( + 'server', + messageInDb.serverId, + messageInDb.hubId, + expires, + ); + } + + // TODO: Notify server of blacklist + await interaction.editReply({ embeds: [successEmbed], components: [] }); + } + } +} diff --git a/src/commands/context-menu/deleteMsg.ts b/src/commands/context-menu/deleteMsg.ts new file mode 100644 index 00000000..04a28bed --- /dev/null +++ b/src/commands/context-menu/deleteMsg.ts @@ -0,0 +1,60 @@ +import { ApplicationCommandType, CacheType, ContextMenuCommandInteraction, RESTPostAPIApplicationCommandsJSONBody } from 'discord.js'; +import BaseCommand from '../BaseCommand.js'; +import db from '../../utils/Db.js'; +import { checkIfStaff } from '../../utils/Utils.js'; +import { emojis } from '../../utils/Constants.js'; + +export default class DeleteMessage extends BaseCommand { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + type: ApplicationCommandType.Message, + name: 'Delete Message', + dm_permission: false, + }; + + async execute(interaction: ContextMenuCommandInteraction) { + await interaction.deferReply({ ephemeral: true }); + + const messageInDb = await db?.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: { equals: interaction.targetId } } } }, + include: { hub: true }, + }); + + if (!messageInDb) return await interaction.editReply('Unknown Message. If it has been sent in the past minute, please wait few more seconds and try again.'); + + const interchatStaff = checkIfStaff(interaction.user.id); + if ( + !interchatStaff && + !messageInDb.hub?.moderators.find((m) => m.userId === interaction.user.id) && + messageInDb.hub?.ownerId !== interaction.user.id && + interaction.user.id !== messageInDb.authorId + ) return await interaction.editReply(`${emojis.no} You are not the author of this message.`); + + + // find all the messages through the network + const channelSettingsArr = await db.connectedList.findMany({ + where: { channelId: { in: messageInDb.channelAndMessageIds.map((c) => c.channelId) } }, + }); + const results = messageInDb.channelAndMessageIds.map(async (element) => { + const connection = channelSettingsArr.find((c) => c.channelId === element.channelId); + if (!connection) return false; + + const webhookURL = connection.webhookURL.split('/'); + const webhook = await interaction.client.fetchWebhook(webhookURL[webhookURL.length - 2])?.catch(() => null); + + if (webhook?.owner?.id !== interaction.client.user?.id) return false; + + // finally, delete the message + return await webhook?.deleteMessage(element.messageId, connection.parentId ? connection.channelId : undefined) + .then(() => true) + .catch(() => false); + }); + + const resultsArray = await Promise.all(results); + const deleted = resultsArray.reduce((acc, cur) => acc + (cur ? 1 : 0), 0); + await interaction.editReply(`${emojis.yes} Your message has been deleted from __**${deleted}/${resultsArray.length}**__ servers.`).catch(() => null); + + // log the deleted message for moderation purposes TODO + // if (interaction.inCachedGuild()) networkMessageDelete(interaction.member, interaction.targetMessage); + } + +} \ No newline at end of file diff --git a/src/commands/context-menu/editMsg.ts b/src/commands/context-menu/editMsg.ts new file mode 100644 index 00000000..6daa7a0a --- /dev/null +++ b/src/commands/context-menu/editMsg.ts @@ -0,0 +1,166 @@ +import { + ModalBuilder, + ActionRowBuilder, + TextInputBuilder, + TextInputStyle, + EmbedBuilder, + MessageContextMenuCommandInteraction, + ApplicationCommandType, + RESTPostAPIApplicationCommandsJSONBody, + CacheType, + ModalSubmitInteraction, +} from 'discord.js'; +import db from '../../utils/Db.js'; +import BaseCommand from '../BaseCommand.js'; +import { HubSettingsBitField } from '../../utils/BitFields.js'; +import { emojis } from '../../utils/Constants.js'; +import { checkIfStaff, hasVoted, replaceLinks } from '../../utils/Utils.js'; +import { censor } from '../../utils/Profanity.js'; +import { Interaction } from '../../decorators/Interaction.js'; +import { CustomID } from '../../structures/CustomID.js'; + +export default class DeleteMessage extends BaseCommand { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + type: ApplicationCommandType.Message, + name: 'Edit Message', + dm_permission: false, + }; + + async execute(interaction: MessageContextMenuCommandInteraction) { + const target = interaction.targetMessage; + + if (!(await hasVoted(interaction.user.id)) && !checkIfStaff(interaction.user.id)) { + await interaction.reply({ + content: `${emojis.no} You must [vote]() to use this command.`, + ephemeral: true, + }); + return; + } + + const messageInDb = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: { equals: target.id } } } }, + include: { hub: true }, + }); + + if (!messageInDb) { + await interaction.reply({ + content: 'This message has expired. If not, please wait a few seconds and try again.', + ephemeral: true, + }); + return; + } + else if (interaction.user.id != messageInDb?.authorId) { + await interaction.reply({ + content: 'You are not the author of this message.', + ephemeral: true, + }); + return; + } + + const modal = new ModalBuilder() + .setCustomId(new CustomID().setIdentifier('editMsg').addArgs(target.id).toString()) + .setTitle('Edit Message') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setRequired(true) + .setCustomId('newMessage') + .setStyle(TextInputStyle.Paragraph) + .setLabel('Please enter your new message.') + .setValue(`${target.content || target.embeds[0]?.description}`) + .setMaxLength(950), + ), + ); + + await interaction.showModal(modal); + // TODO + // if (interaction.inCachedGuild()) networkMsgUpdate(interaction.member, target, newMessage); + } + + @Interaction('editMsg') + async handleModals(interaction: ModalSubmitInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + const messageId = customId.args[0]; + + const target = await interaction.channel?.messages.fetch(messageId).catch(() => null); + if (!target) return await interaction.reply('Unknown Message.'); + + const messageInDb = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: { equals: target.id } } } }, + include: { hub: true }, + }); + if (!messageInDb?.hub) return await interaction.reply('Unknown Message.'); + + // defer it because it takes a while to edit the message + await interaction.deferReply({ ephemeral: true }); + + // get the new message input by user + const userInput = interaction.fields.getTextInputValue('newMessage'); + const hubSettings = new HubSettingsBitField(messageInDb.hub.settings); + const newMessage = hubSettings.has('HideLinks') ? replaceLinks(userInput) : userInput; + const censoredNewMessage = censor(newMessage); + + if ( + newMessage.includes('discord.gg') || + newMessage.includes('discord.com/invite') || + newMessage.includes('dsc.gg') + ) { + await interaction.editReply( + `${emojis.no} Do not advertise or promote servers in the network. Set an invite in \`/network manage\` instead!`, + ); + return; + } + + const guild = await interaction.client.fetchGuild(messageInDb.serverId); + + // if the message being edited is in compact mode + // then we create a new embed with the new message and old reply + // else we just use the old embed and replace the description + const newEmbed = target.content + ? new EmbedBuilder() + .setAuthor({ name: target.author.username, iconURL: target.author.displayAvatarURL() }) + .setDescription(newMessage) + .setColor(target.member?.displayHexColor ?? 'Random') + .addFields( + target.embeds[0] + ? [{ name: 'Reply-to', value: `${target.embeds[0].description}` }] + : [], + ) + .setFooter({ text: `Server: ${guild?.name}` }) + : EmbedBuilder.from(target.embeds[0]).setDescription(newMessage); + + const censoredEmbed = EmbedBuilder.from(newEmbed).setDescription(censoredNewMessage); + + // find all the messages through the network + const channelSettingsArr = await db.connectedList.findMany({ + where: { channelId: { in: messageInDb.channelAndMessageIds.map((c) => c.channelId) } }, + }); + + const results = messageInDb.channelAndMessageIds.map(async (element) => { + const channelSettings = channelSettingsArr.find((c) => c.channelId === element.channelId); + if (!channelSettings) return false; + + const webhookURL = channelSettings.webhookURL.split('/'); + const webhook = await interaction.client.fetchWebhook(webhookURL[webhookURL.length - 2])?.catch(() => null); + + if (!webhook || webhook.owner?.id !== interaction.client.user.id) return false; + + // finally, edit the message + return await webhook + .editMessage(element.messageId, { + threadId: channelSettings.parentId ? channelSettings.channelId : undefined, + embeds: !channelSettings.compact + ? [channelSettings.profFilter ? censoredEmbed : newEmbed] + : undefined, + }) + .then(() => true) + .catch(() => false); + }); + + const resultsArray = await Promise.all(results); + const deleted = resultsArray.reduce((acc, cur) => acc + (cur ? 1 : 0), 0); + await interaction.editReply( + `${emojis.yes} Your message has been edited in __**${deleted}/${resultsArray.length}**__ servers.`, + ); + } +} diff --git a/src/commands/context-menu/messageInfo.ts b/src/commands/context-menu/messageInfo.ts new file mode 100644 index 00000000..c3700648 --- /dev/null +++ b/src/commands/context-menu/messageInfo.ts @@ -0,0 +1,323 @@ +import { + ActionRow, + ActionRowBuilder, + ApplicationCommandType, + AttachmentBuilder, + ButtonBuilder, + ButtonComponent, + ButtonStyle, + EmbedBuilder, + MessageComponentInteraction, + MessageContextMenuCommandInteraction, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import db from '../../utils/Db.js'; +import { stripIndents } from 'common-tags'; +import { profileImage } from 'discord-arts'; +import { colors, emojis } from '../../utils/Constants.js'; +import BaseCommand from '../BaseCommand.js'; +import { CustomID } from '../../structures/CustomID.js'; +import { Interaction } from '../../decorators/Interaction.js'; + +export default class MessageInfo extends BaseCommand { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + type: ApplicationCommandType.Message, + name: 'Message Info', + dm_permission: false, + }; + + async execute(interaction: MessageContextMenuCommandInteraction) { + const target = interaction.targetMessage; + const networkMessage = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: target.id } } }, + include: { hub: true }, + }); + + if (!networkMessage) { + await interaction.reply({ + content: 'Information about this message is no longer available.', + ephemeral: true, + }); + return; + } + const guildConnected = await db.connectedList.findFirst({ where: { serverId: networkMessage.serverId } }); + const author = await interaction.client.users.fetch(networkMessage.authorId); + const server = await interaction.client.fetchGuild(networkMessage.serverId); + + const embed = new EmbedBuilder() + .setDescription( + stripIndents` + ## ${emojis.clipart} Message Info + + **Sent By:** + __${author.discriminator !== '0' ? author.tag : author.username}__ + + **Sent From:** + __${server?.name}__ + + **Message ID:** + __${target.id}__ + + **Sent In (Hub):** + __${networkMessage.hub?.name}__ + + **Message Created:** + + `, + ) + .setThumbnail(`https://cdn.discordapp.com/icons/${server?.id}/${server?.icon}.png`) + .setColor('Random'); + + const buttonsArr = MessageInfo.buildButtons(target.id); + + if (guildConnected?.invite) { + buttonsArr.push( + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setURL(`https://discord.gg/${guildConnected?.invite}`) + .setEmoji(emojis.join) + .setLabel('Join'), + ), + ); + } + + await interaction.reply({ + embeds: [embed], + components: MessageInfo.buildButtons(target.id), + ephemeral: true, + }); + } + + @Interaction('msgInfo') + async handleComponents(interaction: MessageComponentInteraction) { + // create a variable to store the profile card buffer + const customId = CustomID.parseCustomId(interaction.customId); + const messageId = customId.args[0]; + + const networkMessage = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId } } }, + include: { hub: true }, + }); + if (!networkMessage) { + return await interaction.update({ + content: 'Information about this message is no longer available.', + embeds: [], + components: [], + }); + } + + const author = await interaction.client.users.fetch(networkMessage.authorId); + const server = await interaction.client.fetchGuild(networkMessage.serverId); + const guildConnected = await db.connectedList.findFirst({ where: { serverId: server?.id } }); + + if (interaction.isButton()) { + // component builders taken from the original message + const components = [ + ActionRowBuilder.from( + interaction.message.components[0] as ActionRow, + ), + ]; + + if (interaction.message.components[1]) { + components.push( + ActionRowBuilder.from( + interaction.message.components[1] as ActionRow, + ), + ); + } + + // button responses + switch (customId.postfix) { + // server info button + case 'serverInfo': { + if (!server) { + return await interaction.update({ + content: 'Unable to find server!', + embeds: [], + components: [], + }); + } + + const owner = await interaction.client.users.fetch(server.ownerId); + + if (!server) { + await interaction.update({ content: 'Unable to find server!', embeds: [] }); + return; + } + + const createdAt = Math.round(server.createdTimestamp / 1000); + + const iconUrl = server.icon + ? `https://cdn.discordapp.com/icons/${server.id}/${server.icon}.png` + : null; + const bannerUrL = server.icon + ? `https://cdn.discordapp.com/icons/${server.id}/${server.banner}.png` + : null; + const inviteString = guildConnected?.invite + ? `[\`${guildConnected.invite}\`](https://discord.gg/${guildConnected.invite})` + : 'Not Set.'; + + const serverEmbed = new EmbedBuilder() + .setColor(colors.invisible) + .setThumbnail(iconUrl) + .setImage(bannerUrL) + .setDescription( + stripIndents` + ## ${server?.name} + ${server.description || 'No Description.'} + + **Owner:** + __${owner.username}__${owner.discriminator !== '0' ? `#${owner.discriminator}` : ''} + + **Created:** + () + + **Member Count:** + __${server.memberCount}__ + + **Invite:** + __${inviteString}__, + `, + ) + .setFooter({ text: `ID: ${server.id}` }); + + // disable the server info button + MessageInfo.greyOutButton(components[0], 1); + + await interaction.update({ embeds: [serverEmbed], components, files: [] }); + break; + } + + // user info button + case 'userInfo': { + await interaction.deferUpdate(); + const createdAt = Math.round(author.createdTimestamp / 1000); + + const userEmbed = new EmbedBuilder() + .setThumbnail(author.displayAvatarURL()) + .setColor('Random') + .setImage(author.bannerURL() ?? null) + .setDescription( + stripIndents` + ## ${author.username} + __${author.discriminator !== '0' ? author.tag : author.username}__ + + **ID:** + __${author.id}__ + + **Created:** + () + + **Display Name:** + __${author.globalName || 'Not Set.'}__ + + **Hubs Owned:** + __${await db.hubs.count({ where: { ownerId: author.id } })}__ + `, + ) + .setImage('attachment://customCard.png') // link to image that will be generated afterwards + .setTimestamp(); + + // disable the user info button + MessageInfo.greyOutButton(components[0], 2); + + // generate the profile card + const customCard = new AttachmentBuilder(await profileImage(author.id), { + name: 'customCard.png', + }); + + await interaction.editReply({ + // attach the profile card to the message + files: [customCard], + embeds: [userEmbed], + components, + }); + break; + } + + // message info button + case 'info': { + const message = await interaction.channel?.messages.fetch(messageId).catch(() => null); + + if (!message) { + await interaction.update({ + content: 'Unable to find message!', + embeds: [], + components: [], + }); + return; + } + + const embed = new EmbedBuilder() + .setDescription( + stripIndents` + ## ${emojis.clipart} Message Info + + **Sent By:** + __${author.discriminator !== '0' ? author.tag : author.username}__ + + **Sent From:** + __${server?.name}__ + + **Message ID:** + __${message.id}__ + + **Sent In (Hub):** + __${networkMessage.hub?.name}__ + + **Message Created:** + + `, + ) + .setThumbnail( + server?.icon + ? `https://cdn.discordapp.com/icons/${server.id}/${server.icon}.png` + : null, + ) + .setColor('Random'); + + MessageInfo.greyOutButton(components[0], 0); + + await interaction.update({ embeds: [embed], components, files: [] }); + break; + } + + default: + break; + } + } + } + + // utility methods + static greyOutButton(buttons: ActionRowBuilder, disableElement: number) { + buttons.components.forEach((c) => c.setDisabled(false)); + buttons.components[disableElement].setDisabled(true); + } + + static buildButtons(messageId: string) { + return [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId( + new CustomID().setIdentifier('msgInfo', 'info').addArgs(messageId).toString(), + ) + .setLabel('Message Info') + .setStyle(ButtonStyle.Secondary) + .setDisabled(true), + new ButtonBuilder() + .setCustomId( + new CustomID().setIdentifier('msgInfo', 'serverInfo').addArgs(messageId).toString(), + ) + .setLabel('Server Info') + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId( + new CustomID().setIdentifier('msgInfo', 'userInfo').addArgs(messageId).toString(), + ) + .setLabel('User Info') + .setStyle(ButtonStyle.Secondary), + ), + ]; + } +} diff --git a/src/commands/context-menu/translate.ts b/src/commands/context-menu/translate.ts new file mode 100644 index 00000000..e50e5f17 --- /dev/null +++ b/src/commands/context-menu/translate.ts @@ -0,0 +1,148 @@ +import { + ActionRowBuilder, + ApplicationCommandType, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + CacheType, + EmbedBuilder, + MessageContextMenuCommandInteraction, + ModalBuilder, + ModalSubmitInteraction, + RESTPostAPIApplicationCommandsJSONBody, + TextInputBuilder, + TextInputStyle, +} from 'discord.js'; +import db from '../../utils/Db.js'; +import BaseCommand from '../BaseCommand.js'; +import { emojis } from '../../utils/Constants.js'; +import { hasVoted } from '../../utils/Utils.js'; +import { Interaction } from '../../decorators/Interaction.js'; +import { CustomID } from '../../structures/CustomID.js'; +import { supportedLanguages } from '@translate-tools/core/translators/GoogleTranslator/index.js'; +import translator from '../../utils/Translator.cjs'; + +export default class Translate extends BaseCommand { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + type: ApplicationCommandType.Message, + name: 'Translate', + dm_permission: false, + }; + + async execute(interaction: MessageContextMenuCommandInteraction) { + await interaction.deferReply({ ephemeral: true }); + + if (!(await hasVoted(interaction.user.id))) { + return await interaction.editReply( + 'Please [vote](https://top.gg/bot/769921109209907241/vote) for Interchat to use this command, your support is very much appreciated!', + ); + } + + const target = interaction.targetMessage; + + const messageInDb = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: target.id } } }, + }); + + if (!messageInDb) { + return interaction.editReply( + 'This message has expired. If not, please wait a few seconds and try again.', + ); + } + + const messageContent = target.content || target.embeds[0]?.description; + if (!messageContent) return interaction.editReply('This message is not translatable.'); + + const translatedMessage = await translator.translateText(messageContent, 'en', 'auto'); + const embed = new EmbedBuilder() + .setDescription('### Translation Results') + .setColor('Green') + .addFields( + { + name: 'Original Message', + value: messageContent, + inline: true, + }, + { + name: 'Translated Message', + value: translatedMessage, + inline: true, + }, + ) + .setFooter({ text: 'Translations provided may not be accurate.' }); + + await interaction.editReply({ + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId( + new CustomID() + .setIdentifier('translate', 'lang') + .toString(), + ) + .setLabel('Specify Language') + .setStyle(ButtonStyle.Secondary) + .setEmoji('🌐'), + ), + ], + embeds: [embed], + }); + } + + @Interaction('translate') + async handleComponents(interaction: ButtonInteraction) { + const modal = new ModalBuilder() + .setCustomId('translate_modal') + .setTitle('Specify Language') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('from') + .setLabel('From Language') + .setPlaceholder('Input Language Code (e.g. en, fr, de)') + .setStyle(TextInputStyle.Short) + .setMinLength(2), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('to') + .setLabel('To Language') + .setPlaceholder('Input Language Code (e.g. en, fr, de)') + .setStyle(TextInputStyle.Short) + .setMinLength(2), + ), + ); + + await interaction.showModal(modal); + } + + @Interaction('translate_modal') + async handleModals(interaction: ModalSubmitInteraction) { + const originalMessage = interaction.message; + if (!originalMessage) return; + + // get original content the translate embed + const messageContent = originalMessage.embeds[0]?.fields[0].value; + if (!messageContent) return await interaction.reply('This message is not translatable.'); + + + const to = interaction.fields.getTextInputValue('to'); + const from = interaction.fields.getTextInputValue('from'); + if (!supportedLanguages.includes(from) || !supportedLanguages.includes(to)) { + await interaction.reply({ + content: `${emojis.no} Invalid language code. Please use one from the [here](https://cloud.google.com/translate/docs/languages).`, + ephemeral: true, + }); + return; + } + + const newTranslation = await translator.translateText(messageContent, to, from); + const newEmbed = EmbedBuilder.from(originalMessage.embeds[0]).spliceFields(1, 1, { + name: `Translated Message (${to})`, + value: newTranslation, + inline: true, + }); + + await interaction.reply({ embeds: [newEmbed], ephemeral: true }); + } +} diff --git a/src/commands/slash/Information/help.ts b/src/commands/slash/Information/help.ts index 322fe196..5b653290 100644 --- a/src/commands/slash/Information/help.ts +++ b/src/commands/slash/Information/help.ts @@ -10,12 +10,12 @@ import { StringSelectMenuInteraction, } from 'discord.js'; import { colors, emojis } from '../../../utils/Constants.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; import { getCredits, setComponentExpiry } from '../../../utils/Utils.js'; import { CustomID } from '../../../structures/CustomID.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { Interaction } from '../../../decorators/Interaction.js'; -export default class Help extends Command { +export default class Help extends BaseCommand { readonly data = { name: 'help', description: 'Shows all commands (soon) and guides for InterChat.', @@ -45,7 +45,7 @@ export default class Help extends Command { new StringSelectMenuBuilder({ customId: new CustomID('credits:guide', [interaction.user.id]) .setIdentifier('credits', 'guide') - .addData(interaction.user.id) + .addArgs(interaction.user.id) .toString(), options: [ { @@ -115,10 +115,10 @@ export default class Help extends Command { ); } - @ComponentInteraction('credits') - async handleComponent(interaction: StringSelectMenuInteraction) { + @Interaction('credits') + async handleComponents(interaction: StringSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); - if (interaction.user.id !== customId.data[0]) { + if (interaction.user.id !== customId.args[0]) { await interaction.reply({ content: 'This button is not for you.', ephemeral: true, diff --git a/src/commands/slash/Information/invite.ts b/src/commands/slash/Information/invite.ts index df0afcec..23541848 100644 --- a/src/commands/slash/Information/invite.ts +++ b/src/commands/slash/Information/invite.ts @@ -5,11 +5,11 @@ import { ChatInputCommandInteraction, OAuth2Scopes, } from 'discord.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; import { emojis } from '../../../utils/Constants.js'; import { stripIndents } from 'common-tags'; -export default class Invite extends Command { +export default class Invite extends BaseCommand { readonly data = { name: 'invite', description: 'Invite me to your server!', diff --git a/src/commands/slash/Information/rules.ts b/src/commands/slash/Information/rules.ts index a640229e..6b19654c 100644 --- a/src/commands/slash/Information/rules.ts +++ b/src/commands/slash/Information/rules.ts @@ -1,8 +1,8 @@ import { ChatInputCommandInteraction } from 'discord.js'; import { rulesEmbed } from '../../../utils/Constants.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; -export default class Rules extends Command { +export default class Rules extends BaseCommand { readonly data = { name: 'rules', description: 'Sends the network rules for InterChat.', diff --git a/src/commands/slash/Information/stats.ts b/src/commands/slash/Information/stats.ts index 7afd56dd..c57bd4c9 100644 --- a/src/commands/slash/Information/stats.ts +++ b/src/commands/slash/Information/stats.ts @@ -1,6 +1,5 @@ import { ActionRowBuilder, - ApplicationCommandType, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, @@ -8,17 +7,16 @@ import { Status, } from 'discord.js'; import db from '../../../utils/Db.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; import { cpus, totalmem } from 'os'; import { colors, isDevBuild } from '../../../utils/Constants.js'; import { msToReadable } from '../../../utils/Utils.js'; import { stripIndents } from 'common-tags'; -export default class Stats extends Command { +export default class Stats extends BaseCommand { readonly data = { name: 'stats', description: 'View InterChat\'s statistics.', - type: ApplicationCommandType.ChatInput, }; async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/commands/slash/Information/vote.ts b/src/commands/slash/Information/vote.ts index 63fb8b41..6b1b0b92 100644 --- a/src/commands/slash/Information/vote.ts +++ b/src/commands/slash/Information/vote.ts @@ -1,9 +1,9 @@ import { stripIndents } from 'common-tags'; import { ChatInputCommandInteraction, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; import { colors } from '../../../utils/Constants.js'; -export default class Vote extends Command { +export default class Vote extends BaseCommand { readonly data = { name: 'vote', description: 'Voting perks and vote link.', diff --git a/src/commands/slash/Main/blacklist.ts b/src/commands/slash/Main/blacklist.ts new file mode 100644 index 00000000..41bf5f93 --- /dev/null +++ b/src/commands/slash/Main/blacklist.ts @@ -0,0 +1,261 @@ +import { + ApplicationCommandOptionType, + AutocompleteInteraction, + ChatInputCommandInteraction, + Collection, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import BaseCommand from '../../BaseCommand.js'; +import db from '../../../utils/Db.js'; + +export default class BlacklistCommand extends BaseCommand { + // TODO: Put this in readme + static readonly subcommands = new Collection(); + + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + name: 'blacklist', + description: 'Blacklist a user or server from a hub.', + options: [ + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: 'add', + description: 'Add blacklist', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'user', + description: 'Blacklist a user from using your hub.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub to blacklist the user from.', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'user', + description: + 'The user ID to blacklist. User tag also works if they are already cached.', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'The reason for blacklisting the user.', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'duration', + description: 'The duration of the blacklist. Eg. 1d, 1w, 1m, 1y', + required: false, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'server', + description: 'Blacklist a server from using your hub.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub to blacklist the server from.', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'server', + description: 'The server ID to blacklist.', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'The reason for blacklisting the server.', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'duration', + description: 'The duration of the blacklist. Eg. 1d, 1w, 1m, 1y', + required: false, + }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: 'remove', + description: 'Remove a blacklist from your hub.', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'user', + description: 'Remove a user from the blacklist.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub to blacklist the user from.', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'user', + description: 'The user to remove from the blacklist. User tag also works.', + required: true, + autocomplete: true, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'server', + description: 'Remove a server from the blacklist.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub to blacklist the user from.', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'server', + description: 'The server to remove from the blacklist.', + required: true, + autocomplete: true, + }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'list', + description: 'List all blacklists for your hub.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub to blacklist the user from.', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'type', + description: 'The type of blacklist to list.', + required: true, + choices: [ + { name: 'User', value: 'user' }, + { name: 'Server', value: 'server' }, + ], + }, + ], + }, + ], + }; + + async execute(interaction: ChatInputCommandInteraction): Promise { + const subCommand = interaction.options.getSubcommand(); + const isValid = BlacklistCommand.subcommands.get(subCommand); + if (isValid) return await isValid.execute(interaction); + } + + async autocomplete(interaction: AutocompleteInteraction) { + const action = interaction.options.getSubcommand() as 'user' | 'server'; + const focusedHub = interaction.options.get('hub'); + + if (typeof focusedHub?.value !== 'string') return; + + if (focusedHub.focused) { + const hub = await db.hubs.findMany({ + where: { + name: { mode: 'insensitive', contains: focusedHub.value }, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + take: 25, + }); + + const filtered = hub.map(({ name: hubName }) => ({ name: hubName, value: hubName })); + return interaction.respond(filtered); + } + + switch (action) { + case 'user': { + const userOpt = interaction.options.get('user'); + + if (!userOpt?.focused || typeof userOpt.value !== 'string') return; + const userHubMod = await db.hubs.findFirst({ + where: { + name: focusedHub.value, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + }); + + if (!userHubMod) return interaction.respond([]); + + const filteredUsers = await db.blacklistedUsers.findMany({ + where: { + hubs: { some: { hubId: userHubMod.id } }, + OR: [ + { username: { mode: 'insensitive', contains: userOpt.value } }, + { userId: { mode: 'insensitive', contains: userOpt.value } }, + ], + }, + take: 25, + }); + + const choices = filteredUsers.map((user) => { + return { name: user.username, value: user.userId }; + }); + interaction.respond(choices); + break; + } + + case 'server': { + const serverOpt = interaction.options.get('server', true); + const serverHubMod = await db.hubs.findFirst({ + where: { + name: focusedHub.value, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + }); + if (!serverOpt.focused || typeof serverOpt.value !== 'string' || !serverHubMod) return; + + const allServers = await db.blacklistedServers.findMany({ + where: { + hubs: { some: { hubId: serverHubMod.id } }, + OR: [ + { serverName: { mode: 'insensitive', contains: serverOpt.value } }, + { serverId: { mode: 'insensitive', contains: serverOpt.value } }, + ], + }, + take: 25, + }); + const choices = allServers.map(({ serverName, serverId }) => { + return { name: serverName, value: serverId }; + }); + await interaction.respond(choices); + break; + } + } + } +} diff --git a/src/commands/slash/Main/connection.ts b/src/commands/slash/Main/connection.ts index f68b1378..2e79ad00 100644 --- a/src/commands/slash/Main/connection.ts +++ b/src/commands/slash/Main/connection.ts @@ -5,7 +5,6 @@ import { ChannelSelectMenuBuilder, ChannelType, ChatInputCommandInteraction, - EmbedBuilder, MessageComponentInteraction, ModalBuilder, ModalSubmitInteraction, @@ -18,16 +17,16 @@ import { TextInputStyle, ThreadChannel, } from 'discord.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; import db from '../../../utils/Db.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { Interaction } from '../../../decorators/Interaction.js'; import { buildEmbed } from '../../../scripts/network/buildEmbed.js'; import { buildConnectionButtons } from '../../../scripts/network/components.js'; import { emojis } from '../../../utils/Constants.js'; import { CustomID } from '../../../structures/CustomID.js'; -import { disableComponents, getOrCreateWebhook } from '../../../utils/Utils.js'; +import { disableComponents, errorEmbed, getOrCreateWebhook } from '../../../utils/Utils.js'; -export default class Connection extends Command { +export default class Connection extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { name: 'connection', description: 'Manage your connections in this server.', @@ -66,8 +65,8 @@ export default class Connection extends Command { .setCustomId( new CustomID() .setIdentifier('connection', 'settings') - .addData(channelId) - .addData(interaction.user.id) + .addArgs(channelId) + .addArgs(interaction.user.id) .toString(), ) .setPlaceholder('🛠️ Select a setting to toggle') @@ -144,19 +143,17 @@ export default class Connection extends Command { interaction.respond(await Promise.all(filtered)); } - @ComponentInteraction('connection') - async handleComponent(interaction: MessageComponentInteraction) { + @Interaction('connection') + async handleComponents(interaction: MessageComponentInteraction) { const customId = CustomID.parseCustomId(interaction.customId); - const channelId = customId.data[0]; + const channelId = customId.args[0]; - if (customId.data.at(1) && customId.data[1] !== interaction.user.id) { + if (customId.args.at(1) && customId.args[1] !== interaction.user.id) { interaction.reply({ embeds: [ - new EmbedBuilder() - .setColor('Red') - .setDescription( - `${emojis.no} This button is not for you. Execute the command yourself to utilize this button.`, - ), + errorEmbed( + `${emojis.no} This button is not for you. Execute the command yourself to use this button.`, + ), ], ephemeral: true, }); @@ -206,7 +203,7 @@ export default class Connection extends Command { .setCustomId( new CustomID() .setIdentifier('connectionModal', 'invite') - .addData(channelId) + .addArgs(channelId) .toString(), ) .addComponents( @@ -229,8 +226,8 @@ export default class Connection extends Command { .setCustomId( new CustomID() .setIdentifier('connection', 'change_channel') - .addData(channelId) - .addData(interaction.user.id) + .addArgs(channelId) + .addArgs(interaction.user.id) .toString(), ) .setChannelTypes( @@ -297,13 +294,13 @@ export default class Connection extends Command { } } - @ComponentInteraction('connectionModal') - async handleModal(interaction: ModalSubmitInteraction): Promise { + @Interaction('connectionModal') + async handleModals(interaction: ModalSubmitInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); - if (customId.identifier !== 'connectionModal') return; + if (customId.prefix !== 'connectionModal') return; const invite = interaction.fields.getTextInputValue('connInviteField'); - const channelId = customId.data[0]; + const channelId = customId.args[0]; const networkManager = interaction.client.getNetworkManager(); if (!invite) { diff --git a/src/commands/slash/Main/hub.ts b/src/commands/slash/Main/hub.ts index 5d3c924a..4a46ad9e 100644 --- a/src/commands/slash/Main/hub.ts +++ b/src/commands/slash/Main/hub.ts @@ -7,26 +7,48 @@ import { Collection, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; -import Command from '../../Command.js'; +import BaseCommand from '../../BaseCommand.js'; import db from '../../../utils/Db.js'; const hubOption: APIApplicationCommandBasicOption = { + type: ApplicationCommandOptionType.String, name: 'hub', description: 'Choose a hub.', required: true, - type: ApplicationCommandOptionType.String, autocomplete: true, }; -export default class HubCommand extends Command { +export default class Hub extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { name: 'hub', description: 'Manage your hubs.', + dm_permission: false, options: [ { type: ApplicationCommandOptionType.Subcommand, name: 'browse', description: '🔍 Browse public hubs and join them!', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'Search for a hub.', + required: false, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'sort', + description: 'Sort the results.', + required: false, + choices: [ + { + name: 'Most Active', + value: 'most-active', + }, + ], + }, + ], }, { type: ApplicationCommandOptionType.Subcommand, @@ -96,16 +118,30 @@ export default class HubCommand extends Command { description: '🗑️ Delete a hub you own.', options: [hubOption], }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'connections', + description: '📜 List all connected servers to your hub.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'Choose a hub.', + required: true, + autocomplete: true, + }, + ], + }, ], }; // subcommand classes are added to this map in their respective files - static readonly subcommands = new Collection; + static readonly subcommands = new Collection; async execute(interaction: ChatInputCommandInteraction): Promise { - const subcommand = HubCommand.subcommands?.get(interaction.options.getSubcommand()); - subcommand?.execute(interaction); - return; + const subCommand = interaction.options.getSubcommand(); + const isValid = Hub.subcommands?.get(subCommand); + if (isValid) return await isValid.execute(interaction); } async autocomplete(interaction: AutocompleteInteraction): Promise { diff --git a/src/commands/slash/Staff/purge.ts b/src/commands/slash/Staff/purge.ts new file mode 100644 index 00000000..2089920e --- /dev/null +++ b/src/commands/slash/Staff/purge.ts @@ -0,0 +1,254 @@ +import { + APIApplicationCommandBasicOption, + ApplicationCommandOptionType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionFlagsBits, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import db from '../../../utils/Db.js'; +import BaseCommand from '../../BaseCommand.js'; +import { captureException } from '@sentry/node'; +import { stripIndents } from 'common-tags'; +import { emojis } from '../../../utils/Constants.js'; +import { messageData as messageDataCol } from '@prisma/client'; +import { msToReadable } from '../../../utils/Utils.js'; +import Logger from '../../../utils/Logger.js'; + +const limitOpt: APIApplicationCommandBasicOption = { + type: ApplicationCommandOptionType.Integer, + name: 'limit', + description: 'Number of messages to delete. Max: 100', + required: true, + max_value: 100, +}; + +export default class Purge extends BaseCommand { + readonly staffOnly = true; + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + name: 'purge', + description: 'Mass delete network messages. Staff-only', + dm_permission: false, + default_member_permissions: PermissionFlagsBits.ManageGuild.toString(), + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'server', + description: 'Purge network messages sent from a particular server.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'server', + description: 'The ID of the server.', + required: true, + }, + { ...limitOpt }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'user', + description: 'Purge network messages sent by a particular user. Staff-only', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'user', + description: 'The ID of the user.', + required: true, + }, + { ...limitOpt }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'replies', + description: 'Purge messages from the network. Staff-only', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'replied-to', + description: 'Provide the message ID to delete its replies.', + required: true, + }, + { ...limitOpt }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'any', + description: 'Purge messages from the network. Staff-only', + options: [{ ...limitOpt }], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'after', + description: 'Purge messages after a certain message. Staff-only', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'message', + description: 'The ID of the starting message.', + required: true, + }, + { + type: ApplicationCommandOptionType.Integer, + name: 'limit', + description: 'Number of messages to delete. Max: 100, Default: 10', + max_value: 100, + required: false, + }, + ], + }, + ], + }; + + async execute(interaction: ChatInputCommandInteraction) { + const subcommand = interaction.options.getSubcommand(); + const limit = interaction.options.getInteger('limit') || 100; + const { messageData, connectedList } = db; + const channelInHub = await connectedList.findFirst({ + where: { channelId: interaction.channelId, connected: true }, + }); + + if (!channelInHub) { + return await interaction.reply({ + content: 'This channel is not connected to a hub.', + ephemeral: true, + }); + } + + let messagesInDb: messageDataCol[] = []; + + switch (subcommand) { + case 'server': { + const serverId = interaction.options.getString('server', true); + messagesInDb = await messageData.findMany({ + where: { serverId, hubId: channelInHub.hubId }, + orderBy: { id: 'desc' }, + take: limit, + }); + break; + } + + case 'user': { + const authorId = interaction.options.getString('user', true); + messagesInDb = await messageData.findMany({ + where: { authorId, hubId: channelInHub.hubId }, + orderBy: { id: 'desc' }, + take: limit, + }); + break; + } + case 'after': { + const messageId = interaction.options.getString('message', true); + const fetchedMsg = await interaction.channel?.messages.fetch(messageId).catch(() => null); + if (fetchedMsg) { + messagesInDb = await messageData.findMany({ + take: limit, + where: { + timestamp: { gt: fetchedMsg.createdAt }, + }, + }); + } + break; + } + case 'replies': { + const messageId = interaction.options.getString('replied-to', true); + messagesInDb = await messageData.findMany({ + where: { hubId: channelInHub.hubId, reference: { is: { messageId } } }, + take: limit, + }); + break; + } + case 'any': + messagesInDb = await messageData.findMany({ + where: { hubId: channelInHub.hubId }, + orderBy: { id: 'desc' }, + take: limit, + }); + break; + + default: + break; + } + + if (!messagesInDb || messagesInDb.length < 1) { + return await interaction.reply({ + content: 'Unable to locate messages to purge. Maybe they have expired?', + ephemeral: true, + }); + } + + await interaction.deferReply({ fetchReply: true }); + + const startTime = performance.now(); + const allNetworks = await connectedList.findMany({ + where: { hubId: channelInHub.hubId, connected: true }, + }); + + + const promiseResults = allNetworks.map(async (network) => { + try { + // TODO: Fine a better way to do this + // because we are doing this in all the shards, which is double the work + const evalRes = await interaction.client.cluster.broadcastEval(async (client, ctx) => { + const channel = await client.channels.fetch(ctx.channelId); + + if (channel?.type === 0 || channel?.isThread()) { + const messageIds = ctx.messagesInDb.flatMap((dbMsg) => + dbMsg.channelAndMessageIds + .filter(({ channelId }) => channelId === channel.id) + .map(({ messageId }) => messageId), + ); + + if (messageIds.length < 1) return []; + + await channel.bulkDelete(messageIds); + return messageIds; + } + }, { context: { channelId: network.channelId, messagesInDb } }); + + return interaction.client.resolveEval(evalRes) || []; + } + catch (e) { + Logger.error(e); + captureException(e); + } + + return []; + }); + + const results = await Promise.all(promiseResults); + const deletedMessages = results.reduce((acc, cur) => acc + cur.length, 0); + const failedMessages = results.reduce((acc, cur) => (acc + cur.length > 0 ? 0 : 1), 0); + + const resultEmbed = new EmbedBuilder() + .setDescription( + stripIndents` + ### ${emojis.delete} Purge Results + + Finished purging from **${allNetworks.length}** networks in \`${msToReadable(performance.now() - startTime)}\`. + `, + ) + .addFields([ + { name: 'Total Purged', value: `\`\`\`js\n${deletedMessages}\`\`\``, inline: true }, + { name: 'Errored Purges', value: `\`\`\`js\n${failedMessages}\`\`\``, inline: true }, + { name: 'Purge Limit', value: `\`\`\`js\n${limit || 'None'}\`\`\``, inline: true }, + ]) + .setFooter({ + text: `Purged By: ${interaction.user.username}`, + iconURL: interaction.user.avatarURL() || undefined, + }) + .setTimestamp() + .setColor('Red'); + + await interaction.followUp({ embeds: [resultEmbed] }).catch(captureException); + + const succeededMessages = results?.filter((i) => i.length > 0).flat(); + await messageData + .deleteMany({ + where: { channelAndMessageIds: { some: { messageId: { in: succeededMessages } } } }, + }) + .catch(captureException); + } +} diff --git a/src/commands/slash/Support/support.ts b/src/commands/slash/Support/support.ts new file mode 100644 index 00000000..96d51c42 --- /dev/null +++ b/src/commands/slash/Support/support.ts @@ -0,0 +1,51 @@ +import { + ApplicationCommandOptionType, + CacheType, + ChatInputCommandInteraction, + Collection, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import BaseCommand from '../../BaseCommand.js'; + +export default class Support extends BaseCommand { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + name: 'support', + description: 'Send reports/suggestions to InterChat staff/developers.', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'report', + description: + 'Report a user, server, bug, or anything related to the bot to InterChat staff.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'type', + description: 'The type of report.', + required: true, + choices: [ + { name: 'User', value: 'user' }, + { name: 'Server', value: 'server' }, + { name: 'Bug', value: 'bug' }, + { name: 'Other', value: 'other' }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'server', + description: 'Get the invite to the support server.', + }, + ], + }; + + // subcommand classes are added to this map in their respective files + static readonly subcommands = new Collection(); + + async execute(interaction: ChatInputCommandInteraction) { + const subCommand = interaction.options.getSubcommand(); + const isValid = Support.subcommands?.get(subCommand); + if (isValid) return await isValid.execute(interaction); + } +} diff --git a/src/commands/subcommands/blacklist/list.ts b/src/commands/subcommands/blacklist/list.ts new file mode 100644 index 00000000..51162e03 --- /dev/null +++ b/src/commands/subcommands/blacklist/list.ts @@ -0,0 +1,103 @@ +import { APIEmbedField, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import db from '../../../utils/Db.js'; +import BlacklistCommand from '../../slash/Main/blacklist.js'; +import { stripIndents } from 'common-tags'; +import { paginate } from '../../../utils/Pagination.js'; +import { colors } from '../../../utils/Constants.js'; + +export default class ListBlacklists extends BlacklistCommand { + async execute(interaction: ChatInputCommandInteraction) { + const hub = interaction.options.getString('hub', true); + + const hubInDb = await db.hubs.findFirst({ where: { + name: hub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + }); + + if (!hubInDb) { + return await interaction.reply({ + content: 'Unknown hub. Make sure you are the owner or a moderator of the hub.', + ephemeral: true, + }); + } + + const serverOpt = interaction.options.getString('type'); + + const embeds: EmbedBuilder[] = []; + let fields: APIEmbedField[] = []; + + const LIMIT = 5; + let counter = 0; + + // loop through all data + // after counter hits limit (5) assign fields to an embed and push to to embeds array + // reset counter & clear fields array + // repeat until you reach the end + + if (serverOpt == 'server') { + const result = await db.blacklistedServers.findMany({ where: { hubs: { some: { hubId: hubInDb.id } } } }); + + result.forEach((data, index) => { + const hubData = data.hubs.find(({ hubId }) => hubId === hubInDb.id); + fields.push({ + name: data.serverName, + value: stripIndents` + **ServerId:** ${data.serverId} + **Reason:** ${hubData?.reason} + **Expires:** ${!hubData?.expires ? 'Never.' : ``} + `, + }); + + counter++; + if (counter >= LIMIT || index === result.length - 1) { + embeds.push(new EmbedBuilder() + .setFields(fields) + .setColor('#0099ff') + .setAuthor({ + name: 'Blacklisted Servers:', + iconURL: interaction.client.user?.avatarURL()?.toString(), + })); + + counter = 0; + fields = []; + } + }); + } + else if (serverOpt == 'user') { + const result = await db.blacklistedUsers.findMany({ where: { hubs: { some: { hubId: hubInDb.id } } } }); + + result.forEach((data, index) => { + const hubData = data.hubs.find(({ hubId }) => hubId === hubInDb.id); + + fields.push({ + name: data.username, + value: stripIndents` + **UserID:** ${data.userId} + **Reason:** ${hubData?.reason} + **Expires:** ${!hubData?.expires ? 'Never.' : ``} + `, + }); + + counter++; + if (counter >= LIMIT || index === result.length - 1) { + embeds.push(new EmbedBuilder() + .setFields(fields) + .setColor(colors.interchatBlue) + .setAuthor({ + name: 'Blacklisted Users:', + iconURL: interaction.client.user?.avatarURL()?.toString(), + })); + + counter = 0; + fields = []; + } + }); + } + + paginate(interaction, embeds); + } +} \ No newline at end of file diff --git a/src/commands/subcommands/blacklist/server.ts b/src/commands/subcommands/blacklist/server.ts new file mode 100644 index 00000000..37824d40 --- /dev/null +++ b/src/commands/subcommands/blacklist/server.ts @@ -0,0 +1,114 @@ +import { captureException } from '@sentry/node'; +import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { emojis } from '../../../utils/Constants.js'; +import db from '../../../utils/Db.js'; +import BlacklistCommand from '../../slash/Main/blacklist.js'; +import Logger from '../../../utils/Logger.js'; +import BlacklistManager from '../../../structures/BlacklistManager.js'; +import parse from 'parse-duration'; + +export default class UserBlacklist extends BlacklistCommand { + async execute(interaction: ChatInputCommandInteraction) { + const hub = interaction.options.getString('hub', true); + + const hubInDb = await db.hubs.findFirst({ + where: { + name: hub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + }); + + if (!hubInDb) { + return await interaction.reply({ + content: 'Unknown hub. Make sure you are the owner or a moderator of the hub.', + ephemeral: true, + }); + } + + // defer the reply as it may take a while to fetch and stuff + await interaction.deferReply(); + + const blacklistManager = interaction.client.getBlacklistManager(); + const subCommandGroup = interaction.options.getSubcommandGroup(); + const serverOpt = interaction.options.getString('server', true); + + if (subCommandGroup == 'add') { + const reason = interaction.options.getString('reason', true); + const duration = parse(`${interaction.options.getString('duration')}`); + const expires = duration ? new Date(Date.now() + duration) : undefined; + + const serverInBlacklist = await BlacklistManager.fetchServerBlacklist(hubInDb.id, serverOpt); + if (serverInBlacklist) {return await interaction.followUp('The server is already blacklisted.');} + + const server = await interaction.client.guilds.fetch(serverOpt).catch(() => null); + if (!server) return await interaction.followUp('You have inputted an invalid server ID.'); + + try { + await blacklistManager.addServerBlacklist(server.id, hubInDb.id, reason, expires); + } + catch (err) { + Logger.error(err); + captureException(err); + interaction.followUp( + `Failed to blacklist **${server.name}**. Enquire with the bot developer for more information.`, + ); + return; + } + + if (expires && interaction.guildId) {blacklistManager.scheduleRemoval('server', interaction.guildId, hubInDb.id, expires);} + + const successEmbed = new EmbedBuilder() + .setDescription(`${emojis.tick} **${server.name}** has been successfully blacklisted!`) + .setColor('Green') + .addFields( + { + name: 'Reason', + value: reason ? reason : 'No reason provided.', + inline: true, + }, + { + name: 'Expires', + value: expires ? `` : 'Never.', + inline: true, + }, + ); + + await interaction.followUp({ embeds: [successEmbed] }); + + const connected = await db.connectedList.findFirst({ + where: { serverId: serverOpt, hubId: hubInDb.id }, + }); + if (connected) { + // notify the server that they have been blacklisted + const channel = await interaction.client.channels + .fetch(connected.channelId) + .catch(() => null); + if (channel?.isTextBased()) {blacklistManager.notifyBlacklist(channel, hubInDb.id, expires, reason).catch(() => null);} + + // delete the connected channel from db so they can't reconnect + await db.connectedList.delete({ where: { channelId: connected.channelId } }); + } + } + else if (subCommandGroup == 'remove') { + const blacklistedServer = await db.blacklistedServers.findFirst({ + where: { serverId: serverOpt, hubs: { some: { hubId: hubInDb.id } } }, + }); + if (!blacklistedServer) { + return await interaction.followUp({ + content: 'The server is not blacklisted.', + ephemeral: true, + }); + } + + await blacklistManager.removeBlacklist('server', hubInDb.id, blacklistedServer.serverId); + + // Using name from DB since the bot can't access server through API. + await interaction.followUp( + `The server **${blacklistedServer.serverName}** has been removed from the blacklist.`, + ); + } + } +} diff --git a/src/commands/subcommands/blacklist/user.ts b/src/commands/subcommands/blacklist/user.ts new file mode 100644 index 00000000..7419adc8 --- /dev/null +++ b/src/commands/subcommands/blacklist/user.ts @@ -0,0 +1,99 @@ +import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import db from '../../../utils/Db.js'; +import BlacklistCommand from '../../slash/Main/blacklist.js'; +import BlacklistManager from '../../../structures/BlacklistManager.js'; +import { emojis } from '../../../utils/Constants.js'; +import parse from 'parse-duration'; + +export default class Server extends BlacklistCommand { + async execute(interaction: ChatInputCommandInteraction) { + await interaction.deferReply(); + + const hub = interaction.options.getString('hub', true); + + const hubInDb = await db.hubs.findFirst({ where: { + name: hub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + }); + + if (!hubInDb) { + return await interaction.reply({ + content: 'Unknown hub. Make sure you are the owner or a moderator of the hub.', + ephemeral: true, + }); + } + + const subcommandGroup = interaction.options.getSubcommandGroup(); + const userId = interaction.options.getString('user', true); + const reason = interaction.options.getString('reason'); + const duration = parse(`${interaction.options.getString('duration')}`); + + const blacklistManager = interaction.client.getBlacklistManager(); + + if (subcommandGroup == 'add') { + // get ID if user inputted a @ mention + const userOpt = userId.replaceAll(/<@|!|>/g, ''); + // find user through username if they are cached or fetch them using ID + const user = interaction.client.users.cache.find((u) => u.username === userOpt) ?? + await interaction.client.users.fetch(userOpt).catch(() => null); + + if (!user) return interaction.followUp('Could not find user. Use an ID instead.'); + if (user.id === interaction.user.id) return interaction.followUp('You cannot blacklist yourself.'); + if (user.id === interaction.client.user?.id) return interaction.followUp('You cannot blacklist the bot wtf.'); + + const userInBlacklist = await BlacklistManager.fetchUserBlacklist(hubInDb.id, userOpt); + if (userInBlacklist) { + interaction.followUp(`**${user.username}** is already blacklisted.`); + return; + } + + const expires = duration ? new Date(Date.now() + duration) : undefined; + await blacklistManager.addUserBlacklist(hubInDb.id, user.id, String(reason), expires); + if (expires) blacklistManager.scheduleRemoval('user', user.id, hubInDb.id, expires); + blacklistManager.notifyBlacklist(user, hubInDb.id, expires, String(reason)); + + const successEmbed = new EmbedBuilder() + .setDescription(`${emojis.tick} **${user.username}** has been successfully blacklisted!`) + .setColor('Green') + .addFields( + { + name: 'Reason', + value: reason ? reason : 'No reason provided.', + inline: true, + }, + { + name: 'Expires', + value: expires ? `` : 'Never.', + inline: true, + }, + ); + + await interaction.followUp({ embeds: [successEmbed] }); + } + + + else if (subcommandGroup == 'remove') { + const blacklistedUser = await BlacklistManager.fetchUserBlacklist(hubInDb.id, userId); + const user = await interaction.client.users.fetch(userId).catch(() => null); + + if (!blacklistedUser) return interaction.followUp('The inputted user is not blacklisted.'); + + await blacklistManager.removeBlacklist('user', hubInDb.id, blacklistedUser.userId); + await interaction.followUp(`**${user?.username || blacklistedUser?.username}** has been removed from the blacklist.`); + + // TODO: Logging + // if (user) { + // modActions(interaction.user, { + // user, + // action: 'unblacklistUser', + // blacklistedFor: blacklistedUser.hubs.find(({ hubId }) => hubId === hubInDb.id)?.reason, + // hubId: hubInDb.id, + // }); + // } + } + } +} \ No newline at end of file diff --git a/src/commands/subcommands/hub/browse.ts b/src/commands/subcommands/hub/browse.ts new file mode 100644 index 00000000..83ad4d7e --- /dev/null +++ b/src/commands/subcommands/hub/browse.ts @@ -0,0 +1,372 @@ +import { + ChatInputCommandInteraction, + CacheType, + ActionRowBuilder, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + ChannelSelectMenuBuilder, + ChannelType, + EmbedBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ModalSubmitInteraction, + ChannelSelectMenuInteraction, +} from 'discord.js'; +import db from '../../../utils/Db.js'; +import Hub from '../../slash/Main/hub.js'; +import { hubs } from '@prisma/client'; +import { emojis } from '../../../utils/Constants.js'; +import { paginate } from '../../../utils/Pagination.js'; +import { calculateAverageRating, getOrCreateWebhook } from '../../../utils/Utils.js'; +import { showOnboarding } from '../../../scripts/network/onboarding.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { Interaction } from '../../../decorators/Interaction.js'; +import { stripIndents } from 'common-tags'; + +export default class Browse extends Hub { + async execute(interaction: ChatInputCommandInteraction): Promise { + const sortBy = interaction.options.getString('sort') as + | 'connections' + | 'active' + | 'popular' + | 'recent' + | undefined; + const hubName = interaction.options.getString('search') || undefined; + + let sortedHubs: hubs[] = []; + + switch (sortBy) { + case 'popular': + sortedHubs = ( + await db.hubs.findMany({ + where: { name: hubName, private: false }, + include: { connections: true }, + }) + ).sort((a, b) => { + const aAverage = calculateAverageRating(a.rating.map((rating) => rating.rating)); + const bAverage = calculateAverageRating(b.rating.map((rating) => rating.rating)); + return bAverage - aAverage; + }); + break; + case 'recent': + sortedHubs = await db.hubs.findMany({ + where: { name: hubName, private: false }, + orderBy: { createdAt: 'desc' }, + }); + break; + case 'connections': + sortedHubs = await db.hubs.findMany({ + where: { name: hubName, private: false }, + orderBy: { connections: { _count: 'desc' } }, + }); + break; + + case 'active': + default: + sortedHubs = await db.hubs.findMany({ + where: { name: hubName, private: false }, + orderBy: { messages: { _count: 'desc' } }, + }); + break; + } + + const hubList = await Promise.all( + sortedHubs?.map(async (hub) => { + const connections = await db.connectedList + .count({ where: { hubId: hub.id, connected: true } }) + .catch(() => 0); + + return Browse.createHubListingsEmbed(hub, connections); + }), + ); + + if (!hubList || hubList.length === 0) { + interaction.reply({ + content: 'There are no hubs listed here at the moment. Please try again later!', + ephemeral: true, + }); + return; + } + + const paginateBtns = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId( + new CustomID().setIdentifier('hub_browse', 'rate').addArgs(sortedHubs[0].id).toString(), + ) + .setLabel('Rate') + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId( + new CustomID().setIdentifier('hub_browse', 'join').addArgs(sortedHubs[0].id).toString(), + ) + .setLabel('Join') + .setStyle(ButtonStyle.Success), + ); + + paginate(interaction, hubList, { + extraComponents: { + actionRow: [paginateBtns], + updateComponents(pageNumber) { + paginateBtns.components[0].setCustomId( + new CustomID() + .setIdentifier('hub_browse', 'rate') + .addArgs(sortedHubs[pageNumber].id) + .toString(), + ); + paginateBtns.components[1].setCustomId( + new CustomID() + .setIdentifier('hub_browse', 'join') + .addArgs(sortedHubs[pageNumber].id) + .toString(), + ); + + return paginateBtns; + }, + }, + }); + } + + @Interaction('hub_browse') + async handleComponents(interaction: ButtonInteraction | ChannelSelectMenuInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + + if (customId.postfix === 'rate') { + const ratingModal = new ModalBuilder() + .setCustomId( + new CustomID().setIdentifier('hub_browse_modal').addArgs(customId.args[0]).toString(), + ) + .setTitle('Rate Hub') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('rating') + .setLabel('Rating') + .setPlaceholder('Rate the hub from 1-5') + .setMaxLength(1) + .setValue('5') + .setStyle(TextInputStyle.Short) + .setRequired(true), + ), + ); + await interaction.showModal(ratingModal); + } + + const hubDetails = await db.hubs.findFirst({ + where: { id: customId.args[0] }, + include: { connections: true }, + }); + + + if (customId.postfix === 'join') { + if (!hubDetails) { + return await interaction.reply({ + content: 'Hub not found.', + ephemeral: true, + }); + } + + const alreadyJoined = hubDetails.connections.find((c) => c.serverId === interaction.guildId); + if (alreadyJoined) { + interaction.reply({ + content: `You have already joined **${hubDetails.name}** from <#${alreadyJoined.channelId}>!`, + ephemeral: true, + }); + return; + } + + const channelSelect = new ActionRowBuilder().addComponents( + new ChannelSelectMenuBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_browse', 'channel_select') + .addArgs(hubDetails.id) + .toString(), + ) + .setPlaceholder('Select a different channel.') + .setChannelTypes([ + ChannelType.PublicThread, + ChannelType.PrivateThread, + ChannelType.GuildText, + ]), + ); + + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId( + new CustomID().setIdentifier('hub_browse', 'confirm').addArgs(hubDetails.id).toString(), + ) + .setLabel('Confirm') + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(new CustomID().setIdentifier('hub_browse', 'cancel').toString()) + .setLabel('Cancel') + .setStyle(ButtonStyle.Danger), + ); + + // use current channel embed + const embed = new EmbedBuilder() + .setDescription( + stripIndents` + Are you sure you wish to join **${hubDetails.name}** from ${interaction.channel}? + + **Note:** You can always change this later using \`/connection\`. + `, + ) + .setColor('Aqua') + .setFooter({ text: 'Want to use a different channel? Use the dropdown below.' }); + + await interaction.reply({ + embeds: [embed], + components: [channelSelect, buttons], + ephemeral: true, + }); + } + + + else if (interaction.customId === 'cancel') { + await interaction.deleteReply().catch(() => null); + return; + } + + + else if (customId.postfix === 'channel_select' || customId.postfix === 'confirm') { + if (!hubDetails) { + return await interaction.reply({ + content: 'Hub not found.', + ephemeral: true, + }); + } + + if (!interaction.inCachedGuild()) return; + + const channel = interaction.isChannelSelectMenu() + ? (interaction.guild?.channels.cache.get(interaction.values[0])) + : interaction.channel; + + // for type safety + if (channel?.type !== ChannelType.GuildText && !channel?.isThread()) { + await interaction.update(`${emojis.no} Only text and thread channels are supported!`); + return; + } + + if (!interaction.guild?.members.me?.permissionsIn(channel).has(['ManageWebhooks'])) { + await interaction.update( + `${emojis.no} I need to have the \`Manage Webhooks\` permission in ${channel} to connect it to a hub!`, + ); + return; + } + + if (!interaction.member.permissionsIn(channel).has('ManageChannels')) { + await interaction.update( + `${emojis.no} You need to have the \`Manage Channels\` permission in ${channel} to connect it to a hub!`, + ); + return; + } + + if (interaction.customId === 'confirm' || interaction.customId === 'channel_select') { + const channelConnected = await db.connectedList.findFirst({ + where: { channelId: channel.id }, + }); + + if (channelConnected) { + interaction.update({ + content: 'This channel is already connected to another hub!', + embeds: [], + components: [], + }); + return; + } + + // Show new users rules & info about network, also prevents user from joining twice + const onboardingCompleted = await showOnboarding(interaction, hubDetails.name, channel.id); + // if user cancels onboarding or it times out + if (!onboardingCompleted) return await interaction.deleteReply().catch(() => null); + + const webhook = await getOrCreateWebhook(channel); + if (!webhook) return; + + const networkManager = interaction.client.getNetworkManager(); + // finally make the connection + await networkManager.createConnection({ + serverId: channel.guildId, + channelId: channel.id, + parentId: channel.isThread() ? channel.parentId : undefined, + webhookURL: webhook.url, + hub: { connect: { id: hubDetails.id } }, + connected: true, + compact: false, + profFilter: true, + }); + + await interaction.editReply({ + content: `Successfully joined hub ${hubDetails.name} from ${channel}! Use \`/network manage\` to manage your connection. And \`/hub leave\` to leave the hub.`, + embeds: [], + components: [], + }); + } + } + } + + @Interaction('hub_browse_modal') + async handleModals(interaction: ModalSubmitInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + + const rating = parseInt(interaction.fields.getTextInputValue('rating')); + if (isNaN(rating) || rating < 1 || rating > 5) { + return await interaction.reply({ + content: 'Invalid rating. You must enter a number between 1 and 5.', + ephemeral: true, + }); + } + + const hubId = customId.args[0]; + const hub = await db.hubs.findFirst({ where: { id: hubId } }); + if (!hub) { + interaction.reply({ + content: 'Hub not found.', + ephemeral: true, + }); + return; + } + + const userAlreadyRated = hub.rating.find((r) => r.userId === interaction.user.id); + + await db.hubs.update({ + where: { id: hubId }, + data: { + rating: !userAlreadyRated + ? { push: { userId: interaction.user.id, rating } } + : { updateMany: { where: { userId: interaction.user.id }, data: { rating } } }, + }, + }); + + await interaction.reply({ + content: 'Rating submitted. Thank you!', + ephemeral: true, + }); + } + + // utils + static createHubListingsEmbed(hub: hubs, connections?: number) { + return new EmbedBuilder() + .setDescription( + stripIndents` + ### ${hub.name} + ${hub.description} + + **Rating:** ${ + hub.rating?.length > 0 + ? '⭐'.repeat(calculateAverageRating(hub.rating.map((hr) => hr.rating))) + : '-' +} + **Connections:** ${connections ?? 'Unknown.'} + **Created At:** + `, + ) + .setColor('Random') + .setThumbnail(hub.iconUrl) + .setImage(hub.bannerUrl); + } +} diff --git a/src/commands/subcommands/hub/connections.ts b/src/commands/subcommands/hub/connections.ts new file mode 100644 index 00000000..61f2a2e7 --- /dev/null +++ b/src/commands/subcommands/hub/connections.ts @@ -0,0 +1,79 @@ +import { ChatInputCommandInteraction, CacheType, EmbedBuilder } from 'discord.js'; +import Hub from '../../slash/Main/hub.js'; +import { stripIndent } from 'common-tags'; +import { emojis } from '../../../utils/Constants.js'; +import { paginate } from '../../../utils/Pagination.js'; +import db from '../../../utils/Db.js'; + +export default class Connections extends Hub { + async execute(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply(); + + const hub = interaction.options.getString('hub', true); + const allNetworks = await db.connectedList.findMany({ + where: { + hub: { + name: hub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + }, + orderBy: { date: 'asc' }, + }); + + if (allNetworks.length === 0) {return interaction.editReply(`No connected servers yet ${emojis.bruhcat}`);} + + const embeds: EmbedBuilder[] = []; + let itemsPerPage = 5; + + for (let index = 0; index < allNetworks.length; index += 5) { + const current = allNetworks?.slice(index, itemsPerPage); + + let j = index; + let l = index; + itemsPerPage += 5; + + const fields = current.map(async (connection) => { + const evalArr = await interaction.client.cluster.broadcastEval( + async (client, ctx) => { + const server = client.guilds.cache.get(ctx.connection.serverId); + + if (server) { + const channel = await server?.channels.fetch(ctx.connection.channelId); + return { serverName: server?.name, channelName: channel?.name }; + } + }, + { context: { connection } }, + ); + + const evalRes = interaction.client.resolveEval(evalArr); + + const setup = allNetworks.find((settings) => settings.channelId === connection.channelId); + let value = stripIndent` + ServerID: ${connection.serverId} + Channel: #${evalRes?.channelName} \`(${connection.channelId}\`) + `; + if (setup) { + value += '\n' + + stripIndent` + Joined At: + Invite: ${setup.invite ? `https://discord.gg/${setup.invite}` : 'Not Set.'} + `; + } + + return { name: `${++j}. ${evalRes?.serverName}`, value }; + }); + + embeds.push( + new EmbedBuilder() + .setDescription(`Current connected servers: ${++l}-${j} / **${allNetworks.length}**`) + .setColor(0x2f3136) + .setFields(await Promise.all(fields)), + ); + } + + return paginate(interaction, embeds); + } +} diff --git a/src/commands/subcommands/hub/create.ts b/src/commands/subcommands/hub/create.ts new file mode 100644 index 00000000..bb258fb6 --- /dev/null +++ b/src/commands/subcommands/hub/create.ts @@ -0,0 +1,166 @@ +import { + ChatInputCommandInteraction, + CacheType, + ActionRowBuilder, + EmbedBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ModalSubmitInteraction, +} from 'discord.js'; +import Hub from '../../slash/Main/hub.js'; +import db from '../../../utils/Db.js'; +import { stripIndents } from 'common-tags'; +import { Interaction } from '../../../decorators/Interaction.js'; +import { HubSettingsBits } from '../../../utils/BitFields.js'; +import { checkAndFetchImgurUrl, errorEmbed } from '../../../utils/Utils.js'; +import { emojis } from '../../../utils/Constants.js'; + +export default class Create extends Hub { + // TODO: readonly cooldown = 60 * 60 * 1000; + + async execute(interaction: ChatInputCommandInteraction) { + const modal = new ModalBuilder() + .setTitle('Create a hub') + .setCustomId('hub_create_modal') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Name') + .setPlaceholder('Give your hub a name.') + .setMinLength(2) + .setMaxLength(100) + .setStyle(TextInputStyle.Short) + .setCustomId('name'), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('What is the hub about?') + .setPlaceholder('A detailed description about your hub.') + .setMaxLength(1024) + .setStyle(TextInputStyle.Paragraph) + .setCustomId('description'), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Icon') + .setPlaceholder('Set a custom icon for your hub. Must be a imgur link.') + .setMaxLength(300) + .setStyle(TextInputStyle.Short) + .setRequired(false) + .setCustomId('icon'), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Banner') + .setPlaceholder('Set a custom banner for your hub. Must be a imgur link.') + .setMaxLength(300) + .setStyle(TextInputStyle.Short) + .setRequired(false) + .setCustomId('banner'), + ), + // new ActionRowBuilder().addComponents( + // new TextInputBuilder() + // .setLabel('Language') + // .setPlaceholder('Pick a language for this hub.') + // .setStyle(TextInputStyle.Short) + // .setCustomId('language'), + // ), + ); + + await interaction.showModal(modal); + } + + @Interaction('hub_create_modal') + async handleModals(interaction: ModalSubmitInteraction) { + await interaction.deferReply({ ephemeral: true }); + + const name = interaction.fields.getTextInputValue('name'); + const description = interaction.fields.getTextInputValue('description'); + const icon = interaction.fields.getTextInputValue('icon'); + const banner = interaction.fields.getTextInputValue('banner'); + + // if hubName contains "discord", "clyde" "```" then return + if (name.match(/discord|clyde|```/gi)) { + return await interaction.followUp({ + content: + 'Hub name can not contain `discord`, `clyde` or \\`\\`\\` . Please choose another name.', + ephemeral: true, + }); + } + + const hubs = await db.hubs.findMany({ + where: { OR: [{ ownerId: interaction.user.id }, { name }] }, + }); + + if (hubs.find((hub) => hub.name === name)) { + return await interaction.followUp({ + content: `Sorry, name **${name}** is unavailable! Please choose another name.`, + ephemeral: true, + }); + } + else if ( + hubs.reduce((acc, hub) => (hub.ownerId === interaction.user.id ? acc + 1 : acc), 0) >= 3 + ) { + return await interaction.followUp({ + content: + 'You may only create a maximum of **3** hubs at the moment. Please delete one of your existing hubs before creating a new one.', + ephemeral: true, + }); + } + + const iconUrl = icon ? await checkAndFetchImgurUrl(icon) : undefined; + const bannerUrl = banner ? await checkAndFetchImgurUrl(banner) : undefined; + + // TODO create a gif showing how to get imgur links + if (iconUrl === false || bannerUrl === false) { + return await interaction.followUp({ + embeds: [ + errorEmbed( + `${emojis.no} Invalid icon or banner url. Make sure it is a valid imgur link and that it is not a gallery or album.`, + ), + ], + ephemeral: true, + }); + } + + await db.hubs.create({ + data: { + name: name, + description, + private: true, + ownerId: interaction.user.id, + iconUrl: iconUrl ?? interaction.client.user.displayAvatarURL(), + bannerUrl: bannerUrl || undefined, + settings: + HubSettingsBits.SpamFilter | HubSettingsBits.Reactions | HubSettingsBits.BlockNSFW, + }, + }); + + // FIXME this is a temp cooldown until we have a global cooldown system for commands & subcommands + // cooldowns.set(interaction.user.id, Date.now() + ); + const successEmbed = new EmbedBuilder() + .setColor('Green') + .setDescription( + stripIndents` + ### Hub Created! + + Congratulations! Your private hub, **${name}**, has been successfully created. + To join, create an invite using \`/hub invite create\` and share the generated code. Then join using \`/hub join\`. + + - **Generate invite:** \`/hub invite create\` + - **Go public:** \`/hub manage\` + - **Join hub:** \`/hub join\` + - **Edit hub:** \`/hub manage\` + - **Add moderators:** \`/hub moderator add\` + + __Learn more about hubs in our [guide](https://discord-interchat.github.io/docs).__ + `, + ) + + .setFooter({ text: 'Join the support server for help!' }) + .setTimestamp(); + + await interaction.editReply({ embeds: [successEmbed] }); + } +} diff --git a/src/commands/subcommands/hub/delete.ts b/src/commands/subcommands/hub/delete.ts index d426fbc4..4093acc9 100644 --- a/src/commands/subcommands/hub/delete.ts +++ b/src/commands/subcommands/hub/delete.ts @@ -1,13 +1,21 @@ -import { ChatInputCommandInteraction, CacheType, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, ButtonInteraction } from 'discord.js'; +import { + ChatInputCommandInteraction, + CacheType, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + ButtonInteraction, +} from 'discord.js'; import db from '../../../utils/Db.js'; -import HubCommand from '../../slash/Main/hub.js'; +import Hub from '../../slash/Main/hub.js'; import { captureException } from '@sentry/node'; import { emojis } from '../../../utils/Constants.js'; -import { setComponentExpiry } from '../../../utils/Utils.js'; +import { deleteHubs, setComponentExpiry } from '../../../utils/Utils.js'; import { CustomID } from '../../../structures/CustomID.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { Interaction } from '../../../decorators/Interaction.js'; -export default class Delete extends HubCommand { +export default class Delete extends Hub { async execute(interaction: ChatInputCommandInteraction) { const hubName = interaction.options.getString('hub', true); const hubInDb = await db.hubs.findFirst({ where: { name: hubName } }); @@ -21,31 +29,32 @@ export default class Delete extends HubCommand { const confirmEmbed = new EmbedBuilder() .setTitle('Are you sure?') - .setDescription('Are you sure you want to delete this hub? This is a destructive action that will **delete all connections** along with the hub.') + .setDescription( + 'Are you sure you want to delete this hub? This is a destructive action that will **delete all connections** along with the hub.', + ) .setColor('Red'); - const confirmButtons = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setLabel('Confirm') - .setCustomId( - new CustomID() - .setIdentifier('hub_delete', 'confirm') - .addData(interaction.user.id) - .addData(hubName) - .toString(), - ) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel('Cancel') - .setCustomId( - new CustomID() - .setIdentifier('hub_delete', 'cancel') - .addData(interaction.user.id) - .addData(hubName) - .toString(), - ) - .setStyle(ButtonStyle.Secondary), - ); + const confirmButtons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('Confirm') + .setCustomId( + new CustomID() + .setIdentifier('hub_delete', 'confirm') + .addArgs(interaction.user.id) + .addArgs(hubInDb.id) + .toString(), + ) + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setLabel('Cancel') + .setCustomId( + new CustomID() + .setIdentifier('hub_delete', 'cancel') + .addArgs(interaction.user.id) + .addArgs(hubInDb.id) + .toString(), + ) + .setStyle(ButtonStyle.Secondary), + ); await interaction.reply({ embeds: [confirmEmbed], @@ -55,11 +64,12 @@ export default class Delete extends HubCommand { setComponentExpiry(interaction.client.getScheduler(), await interaction.fetchReply(), 10_000); } - @ComponentInteraction('hub_delete') - async handleComponent(interaction: ButtonInteraction) { + @Interaction('hub_delete') + async handleComponents(interaction: ButtonInteraction) { + console.log(interaction.customId); const customId = CustomID.parseCustomId(interaction.customId); - const userId = customId.data[0]; - const hubName = customId.data[1]; + const userId = customId.args[0]; + const hubId = customId.args[1]; if (interaction.user.id !== userId) { return await interaction.reply({ @@ -73,30 +83,39 @@ export default class Delete extends HubCommand { return; } - const hubInDb = await db.hubs.findFirst({ where: { name: hubName, ownerId: interaction.user.id } }); + const hubInDb = await db.hubs.findFirst({ + where: { id: hubId, ownerId: interaction.user.id }, + }); if (!hubInDb) { return await interaction.update({ - content: `Hub **${hubName}** no longer exists.`, + content: 'That hub no longer exists.', embeds: [], components: [], }); } - await interaction.update(`${emojis.loading} Deleting connections, invites, messages and the hub. Please wait...`); + await interaction.update( + `${emojis.loading} Deleting connections, invites, messages and the hub. Please wait...`, + ); try { - await db.hubs.delete({ where: { id: hubInDb.id } }); + await deleteHubs([hubInDb.id]); } catch (e) { - captureException(e, { user: { id: interaction.user.id, username: interaction.user.username } }); - await interaction.editReply('Something went wrong while trying to delete the hub. The developers have been notified.'); + captureException(e, { + user: { id: interaction.user.id, username: interaction.user.username }, + }); + await interaction.editReply({ + content: + 'Something went wrong while trying to delete the hub. The developers have been notified.', + }); return; } await interaction.editReply({ - content:`${emojis.tick} The hub has been successfully deleted.`, + content: `${emojis.tick} The hub has been successfully deleted.`, embeds: [], components: [], }); } -} \ No newline at end of file +} diff --git a/src/commands/subcommands/hub/join.ts b/src/commands/subcommands/hub/join.ts index 2320ce4c..74a455f2 100644 --- a/src/commands/subcommands/hub/join.ts +++ b/src/commands/subcommands/hub/join.ts @@ -1,8 +1,6 @@ -import { ButtonInteraction, CacheType, ChannelType, ChatInputCommandInteraction } from 'discord.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; -import { CustomID } from '../../../structures/CustomID.js'; +import { ChannelType, ChatInputCommandInteraction } from 'discord.js'; import { emojis } from '../../../utils/Constants.js'; -import HubCommand from '../../slash/Main/hub.js'; +import Hub from '../../slash/Main/hub.js'; import db from '../../../utils/Db.js'; import BlacklistManager from '../../../structures/BlacklistManager.js'; import { hubs } from '@prisma/client'; @@ -10,14 +8,9 @@ import { getOrCreateWebhook } from '../../../utils/Utils.js'; import { showOnboarding } from '../../../scripts/network/onboarding.js'; import { stripIndents } from 'common-tags'; -export default class JoinSubCommand extends HubCommand { +export default class JoinSubCommand extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { - if (!interaction.inCachedGuild()) { - return await interaction.reply({ - content: `${emojis.no} This command can only be used in servers!`, - ephemeral: true, - }); - } + if (!interaction.inCachedGuild()) return; const networkManager = interaction.client.getNetworkManager(); const hubName = interaction.options.getString('hub') ?? 'InterChat Central'; @@ -103,6 +96,7 @@ export default class JoinSubCommand extends HubCommand { await networkManager.createConnection({ serverId: channel.guildId, channelId: channel.id, + parentId: channel.isThread() ? channel.parentId : undefined, webhookURL: webhook.url, hub: { connect: { id: hub.id } }, connected: true, @@ -111,7 +105,7 @@ export default class JoinSubCommand extends HubCommand { }); await interaction.editReply({ - content: `${emojis.yes} You have successfully connected to **${hub.name}**. Use \`/connection\` to configure your connection.`, + content: `${emojis.yes} You have successfully joined **${hub.name}** from ${channel}. Use \`/connection\` to configure your connection.`, embeds: [], components: [], }); @@ -119,6 +113,8 @@ export default class JoinSubCommand extends HubCommand { const totalConnections = await db.connectedList.count({ where: { hubId: hub.id, connected: true }, }); + + // announce a new server has joined the hub networkManager.sendToNetwork(hub.id, { content: stripIndents` A new server has joined us! ${emojis.clipart} @@ -126,19 +122,8 @@ export default class JoinSubCommand extends HubCommand { **Server Name:** __${interaction.guild.name}__ **Member Count:** __${interaction.guild.memberCount}__ - We now have **${totalConnections}** servers in the network! + We now have **${totalConnections}** servers in the hub! `, }); } - - @ComponentInteraction('hub_join') - async handleComponent(interaction: ButtonInteraction) { - if (!interaction.isButton()) return; - - const customId = CustomID.parseCustomId(interaction.customId); - - if (customId.identifier === 'join') { - await interaction.reply('hi'); - } - } } diff --git a/src/commands/subcommands/hub/leave.ts b/src/commands/subcommands/hub/leave.ts new file mode 100644 index 00000000..65b9a1cf --- /dev/null +++ b/src/commands/subcommands/hub/leave.ts @@ -0,0 +1,73 @@ +import { + ChatInputCommandInteraction, + CacheType, + MessageComponentInteraction, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, +} from 'discord.js'; +import Hub from '../../slash/Main/hub.js'; +import { Interaction } from '../../../decorators/Interaction.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { emojis } from '../../../utils/Constants.js'; +import db from '../../../utils/Db.js'; +import { setComponentExpiry } from '../../../utils/Utils.js'; + +export default class Leave extends Hub { + async execute(interaction: ChatInputCommandInteraction) { + const channelId = interaction.options.getString('hub', true); + const isChannelConnected = await db.connectedList.findFirst({ where: { channelId } }); + + if (!isChannelConnected) { + return await interaction.reply( + `${emojis.no} The channel <#${channelId}> does not have any networks.`, + ); + } + + const choiceButtons = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setCustomId(new CustomID('hub_leave:yes', [channelId]).toString()) + .setLabel('Yes') + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(new CustomID('hub_leave:no', [channelId]).toString()) + .setLabel('No') + .setStyle(ButtonStyle.Danger), + ]); + + const resetConfirmEmbed = new EmbedBuilder() + .setTitle('Delete Network Connection') + .setDescription( + 'Are you sure? You will have to rejoin the hub to use the network again! All previous connection data will be lost.', + ) + .setColor('Red') + .setFooter({ text: 'Confirm within the next 10 seconds.' }); + + await interaction.reply({ + embeds: [resetConfirmEmbed], + components: [choiceButtons], + fetchReply: true, + }); + + setComponentExpiry(interaction.client.getScheduler(), await interaction.fetchReply(), 10_000); + } + + @Interaction('hub_leave') + async handleComponents(interaction: MessageComponentInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + const channelId = customId.args[0]; + + if (customId.postfix === 'no') { + await interaction.message.delete(); + return; + } + + await db.connectedList.delete({ where: { channelId } }); + await interaction.update({ + content: `${emojis.yes} Deleted network connection from <#${channelId}> and left the hub!`, + embeds: [], + components: [], + }); + } +} diff --git a/src/commands/subcommands/hub/manage.ts b/src/commands/subcommands/hub/manage.ts index ff97cde4..d267cba0 100644 --- a/src/commands/subcommands/hub/manage.ts +++ b/src/commands/subcommands/hub/manage.ts @@ -11,15 +11,15 @@ import { TextInputStyle, } from 'discord.js'; import db from '../../../utils/Db.js'; -import HubCommand from '../../slash/Main/hub.js'; +import Hub from '../../slash/Main/hub.js'; import { hubs, connectedList } from '@prisma/client'; import { stripIndents } from 'common-tags'; import { emojis } from '../../../utils/Constants.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { Interaction } from '../../../decorators/Interaction.js'; import { CustomID } from '../../../structures/CustomID.js'; -import { setComponentExpiry } from '../../../utils/Utils.js'; +import { errorEmbed, setComponentExpiry } from '../../../utils/Utils.js'; -export default class Manage extends HubCommand { +export default class Manage extends Hub { async execute(interaction: ChatInputCommandInteraction) { // the chosen one heh const chosenHub = interaction.options.getString('hub', true); @@ -52,17 +52,20 @@ export default class Manage extends HubCommand { ); } - @ComponentInteraction('hub_manage') - async handleComponent(interaction: StringSelectMenuInteraction) { + @Interaction('hub_manage') + async handleComponents(interaction: StringSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); - if (customId.data[0] !== interaction.user.id) { - await interaction.reply({ content: 'You are not allowed to do that!', ephemeral: true }); + if (customId.args[0] !== interaction.user.id) { + await interaction.reply({ + embeds: [errorEmbed('This dropdown is not for you!')], + ephemeral: true, + }); return; } const hubInDb = await db.hubs.findFirst({ - where: { name: customId.data[1] }, + where: { name: customId.args[1] }, include: { connections: true }, }); @@ -77,7 +80,7 @@ export default class Manage extends HubCommand { .setCustomId( new CustomID() .setIdentifier('hub_manage_modal', 'icon') - .addData(hubInDb.name) + .addArgs(hubInDb.name) .toString(), ) .setTitle('Change Hub Icon') @@ -100,7 +103,7 @@ export default class Manage extends HubCommand { .setCustomId( new CustomID() .setIdentifier('hub_manage_modal', 'description') - .addData(hubInDb.name) + .addArgs(hubInDb.name) .toString(), ) .setTitle('Edit Hub Description') @@ -124,7 +127,7 @@ export default class Manage extends HubCommand { .setCustomId( new CustomID() .setIdentifier('hub_manage_modal', 'banner') - .addData(hubInDb.name) + .addArgs(hubInDb.name) .toString(), ) .setTitle('Set Hub Banner') @@ -167,10 +170,10 @@ export default class Manage extends HubCommand { } } - @ComponentInteraction('hub_manage_modal') - async handleModal(interaction: ModalSubmitInteraction) { + @Interaction('hub_manage_modal') + async handleModals(interaction: ModalSubmitInteraction) { const customId = CustomID.parseCustomId(interaction.customId); - const hubName = customId.data[0]; + const hubName = customId.args[0]; let hubInDb = await db.hubs.findFirst({ where: { @@ -289,8 +292,8 @@ export default class Manage extends HubCommand { .setCustomId( new CustomID() .setIdentifier('hub_manage', 'actions') - .addData(userId) - .addData(hubName) + .addArgs(userId) + .addArgs(hubName) .toString(), ) .addOptions([ diff --git a/src/commands/subcommands/hub/settings.ts b/src/commands/subcommands/hub/settings.ts index a7a4b6bd..542ea0f9 100644 --- a/src/commands/subcommands/hub/settings.ts +++ b/src/commands/subcommands/hub/settings.ts @@ -7,15 +7,16 @@ import { Snowflake, } from 'discord.js'; import db from '../../../utils/Db.js'; -import HubCommand from '../../slash/Main/hub.js'; +import Hub from '../../slash/Main/hub.js'; import { hubs } from '@prisma/client'; import { HubSettingsBitField, HubSettingsString } from '../../../utils/BitFields.js'; import { colors, emojis } from '../../../utils/Constants.js'; -import { ComponentInteraction } from '../../../decorators/Interaction.js'; +import { Interaction } from '../../../decorators/Interaction.js'; import { CustomID } from '../../../structures/CustomID.js'; import { StringSelectMenuInteraction } from 'discord.js'; +import { errorEmbed } from '../../../utils/Utils.js'; -export default class Settings extends HubCommand { +export default class Settings extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { const hubName = interaction.options.getString('hub', true); const hub = await db.hubs.findUnique({ @@ -44,10 +45,10 @@ export default class Settings extends HubCommand { await interaction.reply({ embeds: [embed], components: [selects] }); } - @ComponentInteraction('hub_settings') - async handleComponent(interaction: StringSelectMenuInteraction) { + @Interaction('hub_settings') + async handleComponents(interaction: StringSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); - const hubName = customId.data[0]; + const hubName = customId.args[0]; // respond to select menu const selected = interaction.values[0] as HubSettingsString; @@ -56,7 +57,7 @@ export default class Settings extends HubCommand { // & only allow network channels to be marked as NSFW if (selected === 'BlockNSFW') { return interaction.reply({ - content: `${emojis.no} This setting cannot be changed yet. Please wait for the next update.`, + embeds: [errorEmbed(`${emojis.no} This setting cannot be changed yet. Please wait for the next update.`)], ephemeral: true, }); } @@ -116,8 +117,8 @@ export default class Settings extends HubCommand { .setCustomId( new CustomID() .setIdentifier('hub_settings', 'settings') - .addData(hubName) - .addData(userId) + .addArgs(hubName) + .addArgs(userId) .toString(), ) .setPlaceholder('Select an option') diff --git a/src/commands/subcommands/support/report.ts b/src/commands/subcommands/support/report.ts new file mode 100644 index 00000000..9734c82c --- /dev/null +++ b/src/commands/subcommands/support/report.ts @@ -0,0 +1,277 @@ +import { + ActionRowBuilder, + CacheType, + ChatInputCommandInteraction, + EmbedBuilder, + ForumChannel, + MessageComponentInteraction, + ModalBuilder, + ModalSubmitInteraction, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, + TextInputBuilder, + TextInputStyle, + ThreadChannel, +} from 'discord.js'; +import { stripIndents } from 'common-tags'; +import { channels, colors, emojis } from '../../../utils/Constants.js'; +import Support from '../../slash/Support/support.js'; +import { CustomID } from '../../../structures/CustomID.js'; +import { Interaction } from '../../../decorators/Interaction.js'; + +export default class Report extends Support { + readonly reportModal = new ModalBuilder() + .setTitle('New Report') + .setCustomId(new CustomID().setIdentifier('report_modal').toString()) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('description') + .setLabel('Report Details') + .setPlaceholder('A detailed description of the report.') + .setStyle(TextInputStyle.Paragraph) + .setMinLength(10) + .setMaxLength(950), + ), + ); + + async execute(interaction: ChatInputCommandInteraction) { + const reportType = interaction.options.getString('type', true) as + | 'user' + | 'server' + | 'bug' + | 'other'; + + if (reportType === 'bug') { + const bugSelect = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setMaxValues(2) + .setCustomId(new CustomID().setIdentifier('report', 'bug').toString()) + .setOptions( + new StringSelectMenuOptionBuilder() + .setLabel('Commands') + .setEmoji(emojis.slash) + .setValue('Commands'), + new StringSelectMenuOptionBuilder() + .setLabel('Network') + .setEmoji(emojis.clipart) + .setValue('Network'), + new StringSelectMenuOptionBuilder() + .setLabel('Other (Specify)') + .setEmoji('❓') + .setValue('Other'), + ), + ); + + const bugEmbed = new EmbedBuilder() + .setTitle('Affected Components') + .setDescription('Please choose what component of the bot you are facing issues with.') + .setColor('Random'); + + await interaction.reply({ + embeds: [bugEmbed], + components: [bugSelect], + ephemeral: true, + }); + } + else if (reportType === 'server' || reportType === 'user' || reportType === 'other') { + const modal = new ModalBuilder(this.reportModal) + .setCustomId(new CustomID().setIdentifier('report_modal', reportType).toString()) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('id') + .setLabel('User/Server ID') + .setPlaceholder('The IDs of the user/server you are reporting.') + .setStyle(TextInputStyle.Short) + .setMinLength(17) + .setMaxLength(20), + ), + ); + + await interaction.showModal(modal); + } + } + + @Interaction('report') + async handleComponents(interaction: MessageComponentInteraction) { + if (interaction.isStringSelectMenu()) { + const modal = new ModalBuilder(this.reportModal) + .setCustomId( + new CustomID() + .setIdentifier('report_modal', 'bug') + .addArgs(interaction.values.join(', ')) + .toString(), + ) + .setComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('summary') + .setLabel('Whats the bug about?') + .setPlaceholder('Frequent interaction failures...') + .setStyle(TextInputStyle.Short), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('description') + .setLabel('Detailed Description (OPTIONAL)') + .setPlaceholder( + 'Please describe the steps to reproduce the issue, include any unexpected behavior.', + ) + .setStyle(TextInputStyle.Paragraph) + .setRequired(false) + .setMinLength(17), + ), + ); + + // show modal to collect extra information + await interaction?.showModal(modal); + } + } + + @Interaction('report_modal') + async handleModals(interaction: ModalSubmitInteraction) { + const customId = CustomID.parseCustomId(interaction.customId); + const affected = customId.args[0]; + const reportType = customId.args[0]; + + if (reportType === 'bug') { + const summary = interaction.fields.getTextInputValue('summary'); + const description = interaction.fields.getTextInputValue('description'); + + const bugReportEmbed = new EmbedBuilder() + .setColor(colors.invisible) + .setTitle(summary) + .setDescription(`**Affects:** ${affected}`) + .setThumbnail( + interaction.user.avatarURL({ size: 2048 }) ?? interaction.user.defaultAvatarURL, + ) + .setFooter({ + text: `Reported by ${interaction.user.username} (${interaction.user.id})`, + iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, + }); + + if (description) bugReportEmbed.addFields({ name: 'Details', value: description }); + + // send the bug report to ic central + await interaction.client.cluster.broadcastEval( + async (client, ctx) => { + const bugReportChannel = (await client.channels + .fetch(ctx.bugsChannel) + .catch(() => null)) as ForumChannel | null; + + if (!bugReportChannel) return; + + const appliedTags = bugReportChannel.availableTags + .map((tag) => { + if (ctx.affected.includes(tag.name)) return tag.id; + }) + .filter((tag) => tag !== undefined) as string[]; + + // finally make the post in ic central + await bugReportChannel.threads.create({ + name: summary, + message: { embeds: [bugReportEmbed] }, + appliedTags, + }); + }, + { context: { affected, bugsChannel: channels.bugs } }, + ); + + await interaction.reply({ + content: `${emojis.yes} Successfully submitted report. Join the to view and/or attach screenshots to it.`, + ephemeral: true, + }); + } + else { + const reportChannel = (await interaction.client.channels + .fetch(channels.reports) + .catch(() => null)) as ThreadChannel | null; + + const reportDescription = interaction.fields.getTextInputValue('description'); + + switch (reportType) { + case 'user': { + const Ids = interaction.fields.getTextInputValue('id'); + const reportedUser = await interaction.client.users.fetch(Ids).catch(() => null); + if (!reportedUser) { + await interaction.reply({ + content: stripIndents` + ${emojis.no} I couldn't find a user with that ID.\n\n + **To find a user's ID within the network, please follow these instructions:** + ${emojis.dotYellow} Right click on a message sent from the user in question select \`Apps > User Info\`. Please double-check the ID and try again. + `, + ephemeral: true, + }); + return; + } + + const userReport = new EmbedBuilder() + .setColor('Red') + .setTitle('New User Report') + .setDescription(`Username: ${reportedUser.username}\nUser Id: ${reportedUser.id}`) + .setFields({ name: 'Reason for report', value: reportDescription }) + .setThumbnail(reportedUser.avatarURL({ size: 2048 }) ?? reportedUser.defaultAvatarURL) + .setFooter({ + text: `Reported by ${interaction.user.username} (${interaction.user.id})`, + iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, + }); + await reportChannel?.send({ content: '<@&1088677008260726854>', embeds: [userReport] }); + break; + } + + case 'server': { + const id = interaction.fields.getTextInputValue('id'); + const reportedServer = await interaction.client.fetchGuild(id).catch(() => null); + if (!reportedServer) { + await interaction.reply({ + content: stripIndents` + ${emojis.no} I couldn't find a server with that ID.\n + **To find a server ID within the network, please follow these instructions:** + ${emojis.dotYellow} Right click on a message sent by the server in question and select \`Apps > Server Info\`. Please double-check the ID and try again. + `, + ephemeral: true, + }); + return; + } + + const serverReport = new EmbedBuilder() + .setColor('Red') + .setTitle('New Server Report') + .setDescription(`Server Name: ${reportedServer.name}\nServer Id: ${reportedServer.members}`) + .setFields({ name: 'Reason for report', value: reportDescription }) + .setThumbnail(`https://cdn.discordapp.com/icons/${reportedServer.id}/${reportedServer.icon}.png?size=2048`) + .setFooter({ + text: `Reported by ${interaction.user.username} (${interaction.user.id})`, + iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, + }); + await reportChannel?.send({ + content: '<@&1088677008260726854>', + embeds: [serverReport], + }); + break; + } + default: { + const otherReport = new EmbedBuilder() + .setColor('Random') + .setTitle('New Report') + .setDescription('**Type:** Other') + .setFields({ name: 'Description', value: reportDescription }) + .setFooter({ + text: `Reported by ${interaction.user.username} (${interaction.user.id})`, + iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, + }); + await reportChannel?.send({ + content: '<@&1088677008260726854>', + embeds: [otherReport], + }); + break; + } + } + await interaction.reply({ + content: 'Report submitted. Join the support server to get updates on your report.', + ephemeral: true, + }); + } + } +} diff --git a/src/commands/subcommands/support/server.ts b/src/commands/subcommands/support/server.ts new file mode 100644 index 00000000..b871ce4d --- /dev/null +++ b/src/commands/subcommands/support/server.ts @@ -0,0 +1,14 @@ +import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { colors } from '../../../utils/Constants.js'; +import Support from '../../slash/Support/support.js'; + +export default class SupportServer extends Support { + async execute(interaction: ChatInputCommandInteraction) { + const embed = new EmbedBuilder() + .setTitle('InterChat Central') + .setDescription('[Click Here]()') + .setColor(colors.interchatBlue) + .setTimestamp(); + await interaction.reply({ embeds: [embed] }); + } +} \ No newline at end of file diff --git a/src/decorators/Interaction.ts b/src/decorators/Interaction.ts index d91aead2..80107923 100644 --- a/src/decorators/Interaction.ts +++ b/src/decorators/Interaction.ts @@ -1,16 +1,20 @@ -import { interactionsMap } from '../commands/Command.js'; +import { MessageComponentInteraction, ModalSubmitInteraction } from 'discord.js'; +import { interactionsMap } from '../commands/BaseCommand.js'; -// Decorator function to call a specified method on interactionCreate -export function ComponentInteraction(customId: string): MethodDecorator { +export type InteractionFunction = ( + interaction: MessageComponentInteraction | ModalSubmitInteraction, +) => Promise | void; + +// Decorator function to call a specified method when an interaction is created (ie. interactionCreate event) +export function Interaction(customId: string): MethodDecorator { return function( targetClass: Record, propertyKey: string | symbol, descriptor: PropertyDescriptor, ) { - const originalMethod = descriptor.value; + const originalMethod = descriptor.value as InteractionFunction; // NOTE: It is not possible to access other class properties for decorator methods // so don't try to access `this.` in any decorator method body interactionsMap.set(customId, originalMethod); }; } - diff --git a/src/index.ts b/src/index.ts index 9405ed21..721a1f44 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,9 +84,9 @@ manager.on('clusterCreate', async (cluster) => { const scheduler = new Scheduler(); // update top.gg stats every 10 minutes - scheduler.addTask('syncBotlistStats', 60 * 10_000, syncBotlistStats); // every 10 minutes - scheduler.addTask('deleteExpiredInvites', 60 * 60 * 1_000, deleteExpiredInvites); // every hour - scheduler.addTask('deleteOldMessages', 60 * 60 * 12_000, deleteOldMessages); // every 12 hours + scheduler.addRecurringTask('syncBotlistStats', 60 * 10_000, syncBotlistStats); // every 10 minutes + scheduler.addRecurringTask('deleteExpiredInvites', 60 * 60 * 1_000, deleteExpiredInvites); // every hour + scheduler.addRecurringTask('deleteOldMessages', 60 * 60 * 12_000, deleteOldMessages); // every 12 hours // remove expired blacklists or set new timers for them const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; diff --git a/src/scripts/network/components.ts b/src/scripts/network/components.ts index f6a82bce..2c7a2f0b 100644 --- a/src/scripts/network/components.ts +++ b/src/scripts/network/components.ts @@ -22,8 +22,8 @@ export function buildConnectionButtons(connected: boolean | undefined, channelId .setCustomId( new CustomID() .setIdentifier('connection', 'toggle') - .addData(channelId) - .addData(opts?.userId ?? '') + .addArgs(channelId) + .addArgs(opts?.userId ?? '') .toString(), ) .setLabel(connected ? 'Disconnect' : 'Reconnect') diff --git a/src/structures/BlacklistManager.ts b/src/structures/BlacklistManager.ts index 15866347..ebc47ee9 100644 --- a/src/structures/BlacklistManager.ts +++ b/src/structures/BlacklistManager.ts @@ -77,8 +77,7 @@ export default class BlacklistManager { this.scheduler.addTask(name, expires, execute); } - /* static methods (can be access without initializing class) */ - static async notifyBlacklist( + async notifyBlacklist( userOrChannel: User | TextBasedChannel, hubId: string, expires?: Date, @@ -135,7 +134,7 @@ export default class BlacklistManager { } - static async addUserBlacklist( + async addUserBlacklist( hubId: string, userId: string, reason: string, @@ -148,7 +147,7 @@ export default class BlacklistManager { const dbUser = await db.blacklistedUsers.findFirst({ where: { userId: user.id } }); const hubs = dbUser?.hubs.filter((i) => i.hubId !== hubId) || []; - hubs?.push({ expires: expires || null, reason, hubId }); + hubs?.push({ expires: expires ?? null, reason, hubId }); const updatedUser = await db.blacklistedUsers.upsert({ where: { @@ -169,13 +168,13 @@ export default class BlacklistManager { } - static async addServerBlacklist( - client: SuperClient, + async addServerBlacklist( serverId: string, hubId: string, reason: string, expires?: Date, ) { + const client = SuperClient.getInstance(); const guild = await client.fetchGuild(serverId); if (!guild) return; diff --git a/src/structures/CommandManager.ts b/src/structures/CommandManager.ts index a438efd6..bb531f91 100644 --- a/src/structures/CommandManager.ts +++ b/src/structures/CommandManager.ts @@ -2,11 +2,12 @@ import fs from 'fs'; import path from 'path'; import Factory from '../Factory.js'; import Logger from '../utils/Logger.js'; -import Command from '../commands/Command.js'; +import BaseCommand from '../commands/BaseCommand.js'; import { emojis } from '../utils/Constants.js'; import { CustomID } from './CustomID.js'; import { Interaction } from 'discord.js'; import { captureException } from '@sentry/node'; +import { errorEmbed } from '../utils/Utils.js'; const __filename = new URL(import.meta.url).pathname; const __dirname = path.dirname(__filename); @@ -24,6 +25,15 @@ export default class CommandManager extends Factory { if (command?.autocomplete) command.autocomplete(interaction); } else if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { + // const cooldown = this.client.commandCooldowns.get(interaction.user.id); + // if (cooldown && cooldown > Date.now()) { + // return await interaction.reply({ + // content: `You are on a cooldown! Use this command again .`, + // ephemeral: true, + // }); + // } + + // run the command this.client.commands.get(interaction.commandName)?.execute(interaction); } else { @@ -31,24 +41,23 @@ export default class CommandManager extends Factory { // for components have own component collector const ignoreList = ['page_', 'onboarding_']; - if (ignoreList.includes(customId.identifier)) { + if (ignoreList.includes(customId.prefix)) { return; } // component decorator stuff - const handler = this.client.components.find((_, key) => - key.startsWith(customId.identifier), - ); + const handler = this.client.interactions.get(customId.prefix); - if (!handler) { + if (!handler || (customId.expiry && customId.expiry < Date.now())) { await interaction.reply({ - content: `${emojis.no} This is no longer usable.`, + embeds: [errorEmbed(`${emojis.no} This is no longer usable.`)], ephemeral: true, }); return; } - handler(interaction); + // call function that handles the component + await handler(interaction); } } catch (e) { @@ -76,10 +85,10 @@ export default class CommandManager extends Factory { } // If the item is a .js file, read its contents - else if (file.endsWith('.js') && file !== 'Command.js') { + else if (file.endsWith('.js') && file !== 'BaseCommand.js') { // initializing it will automatically add the command to the clientCommands map const imported = await import(filePath); - const command = new imported.default() as Command; + const command = new imported.default() as BaseCommand; command.loadCommand(); command.loadSubcommands(); } diff --git a/src/structures/CustomID.ts b/src/structures/CustomID.ts index db47d028..1c75188e 100644 --- a/src/structures/CustomID.ts +++ b/src/structures/CustomID.ts @@ -1,3 +1,10 @@ +interface ParsedCustomId { + prefix: string; + postfix: string; + expiry?: number; + args: string[]; +} + export class CustomID { private customId: string; private data: string[]; @@ -7,50 +14,77 @@ export class CustomID { this.data = data; } + /** + * Sets the identifier of the custom ID. + * @param prefix - The prefix for the custom ID. + * @param postfix - The postfix for the custom ID (optional). + * @returns CustomID - The CustomID instance for method chaining. + */ setIdentifier(prefix: string, postfix?: string): CustomID { - this.customId = prefix + (postfix ? `:${postfix}` : ''); + this.customId = `${prefix}${postfix ? `:${postfix}` : ''}`; return this; } - addData(value: string): CustomID { + /** + * Adds an argument to the custom ID. + * @param value - The value to add as an argument. + * @returns CustomID - The CustomID instance for method chaining. + */ + addArgs(value: string): CustomID { if (!value) return this; if (value.includes('&')) { - throw new TypeError('Custom ID data cannot contain "&"'); + throw new TypeError('Invalid custom ID argument: The custom ID cannot contain "&".'); } this.customId += `&${value}`; return this; } - static parseCustomId(customId: string) { - const parsed = { - identifier: '', - postfix: '', - // expiry: undefined as Date | undefined, - data: [] as string[], - }; + /** + * Sets the expiry date for the component. + * @param date - The expiry date. + * @returns CustomID - The CustomID instance for method chaining. + */ + setExpiry(date: Date): CustomID { + this.addArgs(`ex=${date.getTime()}`); + return this; + } - for (const [index, part] of customId.split('&').entries()) { - if (index === 0) { - const [identifier, postfix] = part.split(':'); - parsed.identifier = identifier; - parsed.postfix = postfix; - } - // else if (part.startsWith('ex=')) { - // const expiry = parseInt(part.split('=')[1]); - // if (isNaN(expiry)) continue; - - // parsed.expiry = new Date(parseInt(part.split('=')[1])); - // } - else { - parsed.data.push(part); - } - } + /** + * Parses a custom ID in the specified format. + * @param customId - The custom ID to parse. + * @returns ParsedCustomId - The parsed custom ID object. + */ + static parseCustomId(customId: string): ParsedCustomId { + // Split the customId by '&' + const split = customId.split('&'); + + // Extract prefix and postfix + const [prefix, ...postfix] = split[0].split(':'); + + // Extract expiry from arguments + const expiryArg = split.slice(1).find((arg) => arg.startsWith('ex=')); + const expiry = expiryArg ? parseInt(expiryArg.replace('ex=', ''), 10) : undefined; + + // Filter out 'ex=' arguments and store the rest in 'args' + const args = split.slice(1).filter((arg) => !arg.startsWith('ex=')); + + const parsed: ParsedCustomId = { + prefix, + postfix: postfix.join(':'), + expiry, + args, + }; return parsed; } + + /** + * Converts the CustomID instance to its string representation. + * @returns string - The string representation of the CustomID. + */ toString() { let str = `${this.customId}`; if (this.data.length > 0) this.data.forEach((element) => (str += `&${element}`)); diff --git a/src/structures/NetworkManager.ts b/src/structures/NetworkManager.ts index 61137228..2ef6476f 100644 --- a/src/structures/NetworkManager.ts +++ b/src/structures/NetworkManager.ts @@ -2,9 +2,12 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, + Collection, EmbedBuilder, HexColorString, Message, + MessageCreateOptions, + User, WebhookMessageCreateOptions, } from 'discord.js'; import Factory from '../Factory.js'; @@ -28,7 +31,17 @@ export interface Networks extends connectedList { hub: hubs | null; } +interface AntiSpamUserOpts { + timestamps: number[]; + infractions: number; +} + +const WINDOW_SIZE = 5000; +const MAX_STORE = 3; + export default class NetworkManager extends Factory { + private antiSpamMap = new Collection(); + public async handleNetworkMessage(message: NetworkMessage) { const isNetworkMessage = await db.connectedList.findFirst({ where: { channelId: message.channel.id, connected: true }, @@ -38,35 +51,33 @@ export default class NetworkManager extends Factory { // check if the message was sent in a network channel if (!isNetworkMessage?.hub) return; - // loop through all connections and send the message - const allConnections = await this.fetchHubNetworks({ hubId: isNetworkMessage.hubId }); const settings = new HubSettingsBitField(isNetworkMessage.hub.settings); - const checksPassed = await this.runChecks(message, settings); + const checksPassed = await this.runChecks(message, settings, isNetworkMessage.hubId); if (!checksPassed) return; + const allConnections = await this.fetchHubNetworks({ hubId: isNetworkMessage.hubId }); + message.censoredContent = censor(message.content); const attachment = message.attachments.first(); - const attachmentURL = attachment - ? `attachment://${attachment.name}` - : await this.getAttachmentURL(message); + const attachmentURL = attachment ? attachment.url : await this.getAttachmentURL(message); if (attachmentURL) { + const reaction = await message.react(emojis.loading).catch(() => null); + const nsfwDetector = this.client.getNSFWDetector(); const predictions = await nsfwDetector.analyzeImage( attachment ? attachment.url : attachmentURL, ); if (predictions && nsfwDetector.isUnsafeContent(predictions)) { - message.react(emojis.loading); - const nsfwEmbed = new EmbedBuilder() .setTitle('NSFW Image Detected') .setDescription( stripIndents` I have identified this image as NSFW (Not Safe For Work). Sharing NSFW content is against our network guidelines. Refrain from posting such content here. - **NSFW Prediction:** ${predictions[0].className} - ${Math.round(predictions[0].probability * 100)}%`, + **NSFW Detected:** ${Math.round(predictions[0].probability * 100)}%`, ) .setFooter({ text: 'Please be aware that AI predictions can be inaccurate at times, and we cannot guarantee perfect accuracy in all cases. 😔', @@ -76,6 +87,10 @@ export default class NetworkManager extends Factory { return await message.reply({ embeds: [nsfwEmbed] }); } + + reaction?.remove().catch(() => null); + // mark that the attachment url is being used + message.react('🔗').catch(() => null); } // fetch the referred message (message being replied to) from discord @@ -86,7 +101,11 @@ export default class NetworkManager extends Factory { where: { channelAndMessageIds: { some: { messageId: referredMessage?.id } } }, }) : undefined; - const referredContent = referenceInDb ? await this.getReferredContent(message) : undefined; + const referredContent = + referenceInDb && referredMessage ? await this.getReferredContent(referredMessage) : undefined; + const referredAuthor = referenceInDb + ? await message.client.users.fetch(referenceInDb.authorId).catch(() => null) + : null; // embeds for the normal mode const { embed, censoredEmbed } = this.buildNetworkEmbed(message, { @@ -118,9 +137,9 @@ export default class NetworkManager extends Factory { `https://discord.com/channels/${connection.serverId}/${reply.channelId}/${reply.messageId}`, ) .setLabel( - referredMessage.author.username.length >= 80 - ? '@' + referredMessage.author.username.slice(0, 76) + '...' - : '@' + referredMessage.author.username, + referredAuthor && referredAuthor.username.length >= 80 + ? '@' + referredAuthor.username.slice(0, 76) + '...' + : '@' + referredAuthor?.username, ), ) : null; @@ -129,7 +148,6 @@ export default class NetworkManager extends Factory { let messageFormat: WebhookMessageCreateOptions = { components: jumpButton ? [jumpButton] : undefined, embeds: [connection.profFilter ? censoredEmbed : embed], - files: attachment ? [attachment] : undefined, username: `${isNetworkMessage.hub?.name}`, avatarURL: isNetworkMessage.hub?.iconUrl, threadId: connection.parentId ? connection.channelId : undefined, @@ -157,14 +175,12 @@ export default class NetworkManager extends Factory { embeds: replyEmbed ? [replyEmbed] : undefined, components: jumpButton ? [jumpButton] : undefined, content: connection.profFilter ? message.censoredContent : message.content, - files: attachment ? [attachment] : undefined, username: message.author.username, avatarURL: message.author.displayAvatarURL(), threadId: connection.parentId ? connection.channelId : undefined, allowedMentions: { parse: [] }, }; } - // send the message const messageOrError = await webhook.send(messageFormat); // return the message and webhook URL to store the message in the db @@ -179,15 +195,23 @@ export default class NetworkManager extends Factory { } }); - message.delete().catch(() => null); + // only delete the message if there is no attachment + // deleting attachments will make the image not show up in the embed (discord removes it from its cdn) + if (!attachment) message.delete().catch(() => null); // store the message in the db await this.storeMessageData(message, await Promise.all(sendResult), isNetworkMessage.hubId); } - public async runChecks(message: Message, settings: HubSettingsBitField): Promise { + public async runChecks( + message: Message, + settings: HubSettingsBitField, + hubId: string, + ): Promise { + const blacklistManager = this.client.getBlacklistManager(); + const isUserBlacklisted = await db.blacklistedUsers.findFirst({ - where: { userId: message.author.id }, + where: { userId: message.author.id, hubs: { some: { hubId: { equals: hubId } } } }, }); if (isUserBlacklisted) return false; @@ -215,6 +239,33 @@ export default class NetworkManager extends Factory { return false; } + const antiSpamResult = this.runAntiSpam(message.author, 3); + if (antiSpamResult) { + if (antiSpamResult.infractions >= 3) { + await blacklistManager.addUserBlacklist( + hubId, + message.author.id, + 'Auto-blacklisted for spamming.', + 60 * 5000, + ); + blacklistManager.scheduleRemoval('user', message.author.id, hubId, 60 * 5000); + blacklistManager + .notifyBlacklist( + message.author, + hubId, + new Date(Date.now() + 60 * 5000), + 'Auto-blacklisted for spamming.', + ) + .catch(() => null); + } + message.react(emojis.timeout).catch(() => null); + return false; + } + + if (message.content.length > 1000) { + message.reply('Please keep your message shorter than 1000 characters long.'); + return false; + } // TODO allow multiple attachments when embeds can have multiple images const attachment = message.attachments.first(); const allowedTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/jpg']; @@ -278,10 +329,15 @@ export default class NetworkManager extends Factory { name: message.author.username, iconURL: message.author.displayAvatarURL(), }) - .setDescription(message.content) + .setDescription(message.content || null) .addFields( opts?.referredContent - ? [{ name: 'Replying To:', value: opts.referredContent ?? 'Unknown.' }] + ? [ + { + name: 'Replying To:', + value: `> ${opts.referredContent.replaceAll('\n', '\n> ')}` ?? 'Unknown.', + }, + ] : [], ) .setFooter({ @@ -292,10 +348,13 @@ export default class NetworkManager extends Factory { .setColor(opts?.embedCol ?? 'Random'); const censoredEmbed = EmbedBuilder.from(embed) - .setDescription(message.censoredContent) - .addFields( + .setDescription(message.censoredContent || null) + .setFields( opts?.referredContent - ? [{ name: 'Replying To:', value: censor(opts.referredContent) ?? 'Unknown.' }] + ? [{ + name: 'Replying To:', + value: `> ${censor(opts.referredContent).replaceAll('\n', '\n> ')}` ?? 'Unknown.', + }] : [], ); @@ -356,7 +415,63 @@ export default class NetworkManager extends Factory { } } - // TODO: Error handlers for these + runAntiSpam(author: User, maxInfractions = MAX_STORE) { + const userInCol = this.antiSpamMap.get(author.id); + const currentTimestamp = Date.now(); + + if (userInCol) { + if (userInCol.infractions >= maxInfractions) { + // resetting count as it is assumed they will be blacklisted right after + this.antiSpamMap.delete(author.id); + return userInCol; + } + + const { timestamps } = userInCol; + + if (timestamps.length === MAX_STORE) { + // Check if all the timestamps are within the window + const oldestTimestamp = timestamps[0]; + const isWithinWindow = currentTimestamp - oldestTimestamp <= WINDOW_SIZE; + + this.antiSpamMap.set(author.id, { + timestamps: [...timestamps.slice(1), currentTimestamp], + infractions: isWithinWindow ? userInCol.infractions + 1 : userInCol.infractions, + }); + this.setSpamTimers(author.id); + if (isWithinWindow) return userInCol; + } + else { + this.antiSpamMap.set(author.id, { + timestamps: [...timestamps, currentTimestamp], + infractions: userInCol.infractions, + }); + } + } + else { + this.antiSpamMap.set(author.id, { + timestamps: [currentTimestamp], + infractions: 0, + }); + this.setSpamTimers(author.id); + } + } + + setSpamTimers(userId: string): void { + const five_min = 60 * 5000; + const userInCol = this.antiSpamMap.get(userId); + const scheduler = this.client.getScheduler(); + const lastMsgTimestamp = userInCol?.timestamps[userInCol.timestamps.length - 1]; + + if (userInCol && lastMsgTimestamp && Date.now() - five_min <= lastMsgTimestamp) { + scheduler.stopTask(`removeFromCol_${userId}`); + } + + scheduler.addRecurringTask(`removeFromCol_${userId}`, new Date(Date.now() + five_min), () => { + this.antiSpamMap.delete(userId); + }); + } + + // TODO: Add Error handlers for these public async fetchHubNetworks(where: { hubId?: string; hubName?: string }) { return await db.connectedList.findMany({ where }); } @@ -371,20 +486,26 @@ export default class NetworkManager extends Factory { ) { return await db.connectedList.update({ where, data }); } + async createConnection(data: Prisma.connectedListCreateInput) { return await db.connectedList.create({ data }); } - async sendToNetwork(hubId: string, message: string | WebhookMessageCreateOptions) { + + async sendToNetwork(hubId: string, message: string | MessageCreateOptions) { const connections = await this.fetchHubNetworks({ hubId }); const res = connections.map(async (connection) => { + const threadId = connection.parentId ? connection.channelId : undefined; + const payload = + typeof message === 'string' ? { content: message, threadId } : { ...message, threadId }; + try { const webhookURL = connection.webhookURL.split('/'); const webhook = await this.client.fetchWebhook( webhookURL[webhookURL.length - 2], webhookURL[webhookURL.length - 1], ); - return webhook.send(message); + return webhook.send(payload); } catch { return null; diff --git a/src/structures/Scheduler.ts b/src/structures/Scheduler.ts index cf3a7e21..b767ac5f 100644 --- a/src/structures/Scheduler.ts +++ b/src/structures/Scheduler.ts @@ -1,33 +1,45 @@ export default class Scheduler { - private tasks: Map void; interval: number; intervalId: NodeJS.Timeout }>; + private tasks: Map void; ms: number; timeout: NodeJS.Timeout }>; constructor() { this.tasks = new Map(); } - addTask(name: string, interval: number | Date, task: () => void): void { + addRecurringTask(name: string, ms: number | Date, task: () => void): void { if (this.tasks.has(name)) { throw new Error(`Task with name ${name} already exists.`); } // if interval is instance of Date, convert it to milliseconds - interval = interval instanceof Date ? interval.getTime() - Date.now() : interval; + ms = ms instanceof Date ? ms.getTime() - Date.now() : ms; - const intervalId = setInterval(task, interval); - this.tasks.set(name, { task, interval, intervalId }); + const intervalId = setInterval(task, ms); + this.tasks.set(name, { task, ms, timeout: intervalId }); + } + + addTask(name: string, ms: number | Date, task: () => void): void { + if (this.tasks.has(name)) { + throw new Error(`Task with name ${name} already exists.`); + } + + // if interval is instance of Date, convert it to milliseconds + ms = ms instanceof Date ? ms.getTime() - Date.now() : ms; + + const timeout = setTimeout(task, ms); + this.tasks.set(name, { task, ms, timeout }); } stopTask(taskName: string): boolean | undefined { const taskInfo = this.tasks.get(taskName); if (taskInfo) { - clearInterval(taskInfo.intervalId); + clearInterval(taskInfo.timeout); return this.tasks.delete(taskName); } return; } stopAllTasks(): void { this.tasks.forEach((taskInfo, taskName) => { - clearInterval(taskInfo.intervalId); + clearInterval(taskInfo.timeout); this.tasks.delete(taskName); }); } diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 81f91a44..66374445 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -7,6 +7,10 @@ import NetworkManager from '../structures/NetworkManager.ts'; import BlacklistManager from '../structures/BlacklistManager.ts'; import CommandManager from '../structures/CommandManager.ts'; +type RemoveMethods = { + [K in keyof T]: T[K] extends (...args: any[]) => any ? never : RemoveMethods; +}; + declare module 'discord.js' { export interface Client { readonly logger: Logger; @@ -17,7 +21,9 @@ declare module 'discord.js' { readonly reactionCooldowns: Collection; readonly cluster: ClusterClient; - fetchGuild(guildId: Snowflake): Promise; + resolveEval: (value: T[]) => T | undefined + + fetchGuild(guildId: Snowflake): Promise | undefined>; getScheduler(): Scheduler; getCommandManager(): CommandManager; getCommandManager(): CommandManager; diff --git a/src/updater/ReactionUpdater.ts b/src/updater/ReactionUpdater.ts index 5f0687c8..c381b5b4 100644 --- a/src/updater/ReactionUpdater.ts +++ b/src/updater/ReactionUpdater.ts @@ -2,14 +2,17 @@ import db from '../utils/Db.js'; import Factory from '../Factory.js'; import { ActionRowBuilder, + AnySelectMenuInteraction, ButtonBuilder, ButtonInteraction, ButtonStyle, ComponentType, + EmbedBuilder, MessageReaction, PartialMessageReaction, PartialUser, Snowflake, + StringSelectMenuBuilder, User, WebhookClient, } from 'discord.js'; @@ -18,8 +21,9 @@ import { sortReactions } from '../utils/Utils.js'; import { HubSettingsBitField } from '../utils/BitFields.js'; import BlacklistManager from '../structures/BlacklistManager.js'; import { CustomID } from '../structures/CustomID.js'; -import { ComponentInteraction } from '../decorators/Interaction.js'; +import { Interaction } from '../decorators/Interaction.js'; import { emojis } from '../utils/Constants.js'; +import { stripIndents } from 'common-tags'; type messageAndHubSettings = messageData & { hub: { settings: number } | null }; @@ -63,10 +67,10 @@ export default class ReactionUpdater extends Factory { // if there already are reactions by others // and the user hasn't reacted yet !emojiAlreadyReacted?.includes(user.id) - // add user to the array - ? ReactionUpdater.addReaction(dbReactions, user.id, reactedEmoji) - // or update the data with a new arr containing userId - : (dbReactions[reactedEmoji] = emojiAlreadyReacted); + ? // add user to the array + ReactionUpdater.addReaction(dbReactions, user.id, reactedEmoji) + : // or update the data with a new arr containing userId + (dbReactions[reactedEmoji] = emojiAlreadyReacted); await db.messageData.update({ where: { id: messageInDb.id }, @@ -77,14 +81,16 @@ export default class ReactionUpdater extends Factory { ReactionUpdater.updateReactions(messageInDb.channelAndMessageIds, dbReactions); } - @ComponentInteraction('reaction_') - async listenForReactionButton(interaction: ButtonInteraction) { + @Interaction('reaction_') + async listenForReactionButton(interaction: ButtonInteraction | AnySelectMenuInteraction) { await interaction.deferUpdate(); + const customId = CustomID.parseCustomId(interaction.customId); const cooldown = interaction.client.reactionCooldowns.get(interaction.user.id); + const messageId = interaction.isButton() ? interaction.message.id : customId.args[0]; const messageInDb = await db.messageData.findFirst({ - where: { channelAndMessageIds: { some: { messageId: interaction.message.id } } }, + where: { channelAndMessageIds: { some: { messageId } } }, include: { hub: { select: { connections: { where: { connected: true } }, settings: true } }, }, @@ -103,43 +109,106 @@ export default class ReactionUpdater extends Factory { // add user to cooldown list interaction.client.reactionCooldowns.set(interaction.user.id, Date.now() + 3000); - const customId = CustomID.parseCustomId(interaction.customId); + const dbReactions = messageInDb.reactions?.valueOf() as { [key: string]: Snowflake[] }; + if (customId.postfix === 'view_all') { - /* */ - } - else { - if (cooldown && cooldown > Date.now()) { - return await interaction.followUp({ - content: `A little quick there! You can react again !`, + const networkMessage = await db.messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId: interaction.message.id } } }, + include: { + hub: { select: { connections: { where: { connected: true } }, settings: true } }, + }, + }); + + if (!networkMessage?.reactions) { + await interaction.followUp({ + content: 'There are no more reactions to view.', ephemeral: true, }); + return; } + const sortedReactions = ReactionUpdater.sortReactions(dbReactions); + let totalReactions = 0; + let reactionString = ''; + const reactionMenu = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId( + new CustomID().setIdentifier('reaction_').addArgs(interaction.message.id).toString(), + ) + .setPlaceholder('Add a reaction'), + ); + + const hubSettings = new HubSettingsBitField(networkMessage.hub?.settings); + if (!hubSettings.has('Reactions')) reactionMenu.components[0].setDisabled(true); + + sortedReactions.forEach((r, index) => { + if (r[1].length === 0 || index >= 10) return; + reactionMenu.components[0].addOptions({ + label: 'React/Unreact', + value: r[0], + emoji: r[0], + }); + totalReactions++; + reactionString += `- ${r[0]}: ${r[1].length}\n`; + }); + + const embed = new EmbedBuilder() + .setThumbnail(interaction.client.user.displayAvatarURL()) + .setDescription( + stripIndents` + ## ${emojis.clipart} Reactions + + ${reactionString || 'No reactions yet!'} + + **Total Reactions:** + __${totalReactions}__ + `, + ) + .setColor('Random'); + + await interaction.followUp({ + embeds: [embed], + components: [reactionMenu], + ephemeral: true, + }); + } + else { if (userBlacklisted) { - await interaction.reply({ + await interaction.followUp({ content: 'You are blacklisted from this hub.', ephemeral: true, }); return; } else if (serverBlacklisted) { - await interaction.reply({ + await interaction.followUp({ content: 'This server is blacklisted from this hub.', ephemeral: true, }); return; } - const reactedEmoji = customId.postfix; - const dbReactions = messageInDb.reactions?.valueOf() as { [key: string]: Snowflake[] }; + if (cooldown && cooldown > Date.now()) { + return await interaction.followUp({ + content: `A little quick there! You can react again !`, + ephemeral: true, + }); + } + + const reactedEmoji = interaction.isStringSelectMenu() + ? interaction.values[0] + : customId.postfix; const emojiAlreadyReacted = dbReactions[reactedEmoji]; if (!emojiAlreadyReacted) { return await interaction.followUp({ - content: `${emojis.no} This is no longer reactable.`, + content: `${emojis.no} This reaction doesn't exist.`, ephemeral: true, }); } + emojiAlreadyReacted.includes(interaction.user.id) ? // If the user already reacted, remove the reaction ReactionUpdater.removeReaction(dbReactions, interaction.user.id, reactedEmoji) @@ -151,6 +220,15 @@ export default class ReactionUpdater extends Factory { data: { reactions: dbReactions }, }); + if (interaction.isStringSelectMenu()) { + // FIXME seems like emojiAlreadyReacted is getting mutated somewhere + const action = emojiAlreadyReacted.includes(interaction.user.id) ? 'reacted' : 'unreacted'; + interaction.followUp({ + content: `You have ${action} with ${reactedEmoji}!`, + ephemeral: true, + }).catch(() => null); + } + // reflect the changes in the message's buttons await ReactionUpdater.updateReactions(messageInDb.channelAndMessageIds, dbReactions); } @@ -234,6 +312,7 @@ export default class ReactionUpdater extends Factory { .catch(() => null); }); } + static runChecks(messageInDb: messageAndHubSettings) { if ( !messageInDb.hub || @@ -274,4 +353,12 @@ export default class ReactionUpdater extends Factory { reactionArr[emoji].splice(userIndex, 1); return reactionArr; } + + static sortReactions(reactions: { [key: string]: string[] }) { + // Sort the array based on the reaction counts + /* { '👍': ['10201930193'], '👎': ['10201930193'] } // before Object.entries + => [ [ '👎', ['10201930193'] ], [ '👍', ['10201930193'] ] ] // after Object.entries + */ + return Object.entries(reactions).sort((a, b) => b[1].length - a[1].length); + } } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 43761402..097a9f59 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -4,8 +4,8 @@ import { normal, badge, mascot } from './JSON/emojis.json'; import { createRequire } from 'module'; import 'dotenv/config'; -const require = createRequire(import.meta.url); -const emotes = require('./JSON/emojis.json'); +const fakeRequire = createRequire(import.meta.url); +const emotes = fakeRequire('./JSON/emojis.json'); export const isDevBuild = process.env.NODE_ENV === 'development'; @@ -21,6 +21,10 @@ export const REGEX = { IMAGE_URL: /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.gif|\.png)/, /** no animated images */ STATIC_IMAGE_URL: /(?:(?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)(?:\.jpg|\.jpeg|\.png)/, + /** ignores giphy and tenor */ + LINKS: /https?:\/\/(?!tenor\.com|giphy\.com)\S+/g, + /** matches imgur urls */ + IMGUR_LINKS: /(?:i\.imgur\.com\/(?!gallery|a|t|user)([^.]+)(?:\.\w+)?|imgur\.com\/(?!gallery|a|t|user)(\w+))/i, }; export const StaffIds = ['597265261665714186', '442653948630007808', '689082827979227160']; @@ -38,7 +42,7 @@ export const URLs = { DOCS: 'https://discord-interchat.github.io/docs', } as const; -export const channel = { +export const channels = { bugs: '1035135196053393418', networklogs: '1156144879869632553', modlogs: '1042265633896796231', diff --git a/src/utils/Pagination.ts b/src/utils/Pagination.ts index bbd81904..39a5cb59 100644 --- a/src/utils/Pagination.ts +++ b/src/utils/Pagination.ts @@ -5,18 +5,17 @@ import { EmbedBuilder, CommandInteraction, ComponentType, - ButtonInteraction, } from 'discord.js'; import { emojis } from './Constants.js'; +import { MessageActionRowComponentBuilder } from 'discord.js'; export interface PaginatorOptions { /** Number in milliseconds */ stopAfter?: number; - /** only supports buttons at the moment */ - extraComponent?: { - actionRow: ActionRowBuilder[]; - updateComponents?(pageNumber: number): ActionRowBuilder; - execute(i: ButtonInteraction): void; + /** it's on you to handle the response */ + extraComponents?: { + actionRow: ActionRowBuilder[]; + updateComponents(pageNumber: number): ActionRowBuilder; }; btnEmojis?: { back: string; @@ -58,9 +57,9 @@ export async function paginate( .setDisabled(pages.length <= index + 1), ]); - const components: ActionRowBuilder[] = [row]; + const components: ActionRowBuilder[] = [row]; - if (options?.extraComponent) components.push(...options.extraComponent.actionRow); + if (options?.extraComponents) components.push(...options.extraComponents.actionRow); const data = { embeds: [pages[index]], @@ -80,17 +79,13 @@ export async function paginate( if (i.customId === 'page_:back') { index--; } - else if (i.customId === 'page_:exit') { + else if (i.customId === 'page_:next') { index++; } - else if (i.customId === 'page_:next') { + else if (i.customId === 'page_:exit') { col.stop(); return; } - else if (options?.extraComponent) { - options.extraComponent.execute(i); - return; - } row.setComponents([ row.components[0].setDisabled(index === 0), @@ -98,14 +93,14 @@ export async function paginate( row.components[2].setDisabled(index === pages.length - 1), ]); - if (options?.extraComponent?.updateComponents) { - components[1] = options.extraComponent.updateComponents(index); + if (options?.extraComponents) { + components[1] = options.extraComponents.updateComponents(index); } - i.update({ - components, - embeds: [pages[index]], - }); + // edit the message only if the customId is one of the paginator buttons + if (i.customId.startsWith('page_:')) { + i.update({ embeds: [pages[index]], components }); + } }); col.on('end', () => { diff --git a/src/utils/Profanity.ts b/src/utils/Profanity.ts index d0ec5577..33fffa8e 100644 --- a/src/utils/Profanity.ts +++ b/src/utils/Profanity.ts @@ -4,8 +4,8 @@ import { createRequire } from 'node:module'; import badwordsType from './JSON/profanity.json'; // create a require a ESM doesn't support importing JSON -const require = createRequire(import.meta.url); -const badwords = require('./JSON/profanity.json') as typeof badwordsType; +const fakeRequire = createRequire(import.meta.url); +const badwords = fakeRequire('./JSON/profanity.json') as typeof badwordsType; /** * Checks if a message contains any bad words. diff --git a/src/utils/RegisterCommands.ts b/src/utils/RegisterCommands.ts index 05dad081..a2b63a7e 100644 --- a/src/utils/RegisterCommands.ts +++ b/src/utils/RegisterCommands.ts @@ -2,7 +2,7 @@ import Logger from './Logger.js'; import CommandManager from '../structures/CommandManager.js'; import { REST, Routes } from 'discord.js'; import { CLIENT_ID, SUPPORT_SERVER_ID } from './Constants.js'; -import { commandsMap } from '../commands/Command.js'; +import { commandsMap } from '../commands/BaseCommand.js'; import 'dotenv/config'; export default async function registerAllCommands(staffOnly = false) { diff --git a/src/utils/Translator.cts b/src/utils/Translator.cts new file mode 100644 index 00000000..a42e9178 --- /dev/null +++ b/src/utils/Translator.cts @@ -0,0 +1,10 @@ +import { GoogleTranslator } from '@translate-tools/core/translators/GoogleTranslator/index.js'; +import { Scheduler } from '@translate-tools/core/scheduling/Scheduler.js'; + +/** Do not use this directly, use scheduler instead to avoid rate limit */ +export const translator = new GoogleTranslator(); +export const scheduler = new Scheduler(translator, { translatePoolDelay: 100 }); + +export async function translateText(text: string, to: string, from = 'auto') { + return await scheduler.translate(text, from, to, { directTranslate: true }); +} \ No newline at end of file diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 8951a358..0e77a70a 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -2,7 +2,9 @@ import { ActionRow, ButtonStyle, ChannelType, + ColorResolvable, ComponentType, + EmbedBuilder, Message, MessageActionRowComponent, NewsChannel, @@ -10,9 +12,10 @@ import { TextChannel, ThreadChannel, } from 'discord.js'; -import { DeveloperIds, StaffIds, SupporterIds, URLs } from './Constants.js'; -import Scheduler from '../structures/Scheduler.js'; +import { DeveloperIds, REGEX, StaffIds, SupporterIds, URLs, colors } from './Constants.js'; import { randomBytes } from 'crypto'; +import Scheduler from '../structures/Scheduler.js'; +import db from './Db.js'; /** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ export function msToReadable(milliseconds: number): string { @@ -142,3 +145,45 @@ export function disableAllComponents( return jsonRow; }); } + +export async function deleteHubs(ids: string[]) { + // delete all relations first and then delete the hub + await db.connectedList.deleteMany({ where: { hubId: { in: ids } } }); + await db.hubInvites.deleteMany({ where: { hubId: { in: ids } } }); + await db.messageData.deleteMany({ where: { hubId: { in: ids } } }); + await db.hubs.deleteMany({ where: { id: { in: ids } } }); +} + +export function replaceLinks(string: string, replaceText = '`[LINK HIDDEN]`') { + return string.replaceAll(REGEX.LINKS, replaceText); +} + +export function errorEmbed(description: string, color: ColorResolvable = colors.invisible) { + return new EmbedBuilder() + .setColor(color) + .setDescription(description.toString()); +} + +export function calculateAverageRating(ratings: number[]): number { + if (ratings.length === 0) return 0; + + const sum = ratings.reduce((acc, cur) => acc + cur, 0); + const average = sum / ratings.length; + return Math.round(average * 10) / 10; +} + +export async function checkAndFetchImgurUrl(url: string): Promise { + const regex = REGEX.IMGUR_LINKS; + const match = url.match(regex); + if (!match || !match[1] && !match[2]) return false; + + const response = await fetch(`https://api.imgur.com/3/image/${match[1] || match[2]}`, { + headers: { + Authorization: `Client-ID ${process.env.IMGUR_CLIENT_ID}`, + }, + }); + const data = await response.json().catch(() => null); + if (!data || data?.data?.nsfw) return false; + + return data.data.link; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c1e7a7c9..6bfba989 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,6 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true }, - "include": ["src/**/*.ts", "src/**/*.json",], + "include": ["src/**/*.ts", "src/**/*.json", "src/utils/Translator.cts",], "exclude": ["node_modules"], } From a2058a7d78cd0f399f45aa8633d6a167bdaaf547 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Thu, 19 Oct 2023 20:03:32 +0530 Subject: [PATCH 04/13] move subcommand files into commands folder --- src/commands/BaseCommand.ts | 27 -- .../Main/{blacklist.ts => blacklist/index.ts} | 4 +- .../Main}/blacklist/list.ts | 8 +- .../Main}/blacklist/server.ts | 10 +- .../Main}/blacklist/user.ts | 8 +- src/commands/slash/Main/hub.ts | 210 ---------- .../{subcommands => slash/Main}/hub/browse.ts | 16 +- .../Main}/hub/connections.ts | 8 +- .../{subcommands => slash/Main}/hub/create.ts | 16 +- .../{subcommands => slash/Main}/hub/delete.ts | 12 +- src/commands/slash/Main/hub/index.ts | 371 ++++++++++++++++++ src/commands/slash/Main/hub/invite.ts | 164 ++++++++ .../{subcommands => slash/Main}/hub/join.ts | 12 +- .../{subcommands => slash/Main}/hub/leave.ts | 12 +- .../{subcommands => slash/Main}/hub/manage.ts | 12 +- src/commands/slash/Main/hub/moderator.ts | 133 +++++++ .../Main}/hub/settings.ts | 14 +- .../Support/{support.ts => support/index.ts} | 2 +- .../Support}/support/report.ts | 27 +- .../Support}/support/server.ts | 4 +- src/structures/CommandManager.ts | 45 ++- src/utils/Constants.ts | 4 +- src/utils/Profanity.ts | 4 +- tsconfig.json | 2 +- 24 files changed, 787 insertions(+), 338 deletions(-) rename src/commands/slash/Main/{blacklist.ts => blacklist/index.ts} (98%) rename src/commands/{subcommands => slash/Main}/blacklist/list.ts (93%) rename src/commands/{subcommands => slash/Main}/blacklist/server.ts (93%) rename src/commands/{subcommands => slash/Main}/blacklist/user.ts (94%) delete mode 100644 src/commands/slash/Main/hub.ts rename src/commands/{subcommands => slash/Main}/hub/browse.ts (96%) rename src/commands/{subcommands => slash/Main}/hub/connections.ts (92%) rename src/commands/{subcommands => slash/Main}/hub/create.ts (93%) rename src/commands/{subcommands => slash/Main}/hub/delete.ts (90%) create mode 100644 src/commands/slash/Main/hub/index.ts create mode 100644 src/commands/slash/Main/hub/invite.ts rename src/commands/{subcommands => slash/Main}/hub/join.ts (92%) rename src/commands/{subcommands => slash/Main}/hub/leave.ts (86%) rename src/commands/{subcommands => slash/Main}/hub/manage.ts (96%) create mode 100644 src/commands/slash/Main/hub/moderator.ts rename src/commands/{subcommands => slash/Main}/hub/settings.ts (92%) rename src/commands/slash/Support/{support.ts => support/index.ts} (96%) rename src/commands/{subcommands => slash/Support}/support/report.ts (91%) rename src/commands/{subcommands => slash/Support}/support/server.ts (80%) diff --git a/src/commands/BaseCommand.ts b/src/commands/BaseCommand.ts index c9002147..340d10f0 100644 --- a/src/commands/BaseCommand.ts +++ b/src/commands/BaseCommand.ts @@ -7,7 +7,6 @@ import { ModalSubmitInteraction, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; -import { existsSync, readdirSync } from 'fs'; import { InteractionFunction } from '../decorators/Interaction.js'; type CommandInteraction = ChatInputCommandInteraction | ContextMenuCommandInteraction; @@ -19,7 +18,6 @@ export default abstract class BaseCommand { abstract readonly data: RESTPostAPIApplicationCommandsJSONBody; readonly staffOnly?: boolean; readonly description?: string; - // wait wtf static readonly subcommands?: Collection; abstract execute(interaction: CommandInteraction): Promise; @@ -37,29 +35,4 @@ export default abstract class BaseCommand { autocomplete(interaction: AutocompleteInteraction) { /**/ } - - loadSubcommands() { - const commandName = this.data.name; - const fullPath = `build/commands/subcommands/${commandName}/`; - if (!existsSync(fullPath)) return; - - readdirSync(fullPath).forEach(async (file) => { - if (file.endsWith('.js')) { - const subcommandFile = await import(`../commands/subcommands/${commandName}/${file}`); - const subcommandInstance: BaseCommand = subcommandFile.default - ? new subcommandFile.default() - : new subcommandFile(); - const parentCommand = Object.getPrototypeOf(subcommandInstance.constructor); - - // create a new instance of the subcommand class - // and set it in the subcommands map of the parent command - parentCommand.subcommands.set(file.replace('.js', ''), subcommandInstance); - } - }); - } - - /** Save command to the `clientCommands` map */ - loadCommand() { - commandsMap.set(this.data.name, this); - } } diff --git a/src/commands/slash/Main/blacklist.ts b/src/commands/slash/Main/blacklist/index.ts similarity index 98% rename from src/commands/slash/Main/blacklist.ts rename to src/commands/slash/Main/blacklist/index.ts index 41bf5f93..b06e4e63 100644 --- a/src/commands/slash/Main/blacklist.ts +++ b/src/commands/slash/Main/blacklist/index.ts @@ -5,8 +5,8 @@ import { Collection, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; -import BaseCommand from '../../BaseCommand.js'; -import db from '../../../utils/Db.js'; +import BaseCommand from '../../../BaseCommand.js'; +import db from '../../../../utils/Db.js'; export default class BlacklistCommand extends BaseCommand { // TODO: Put this in readme diff --git a/src/commands/subcommands/blacklist/list.ts b/src/commands/slash/Main/blacklist/list.ts similarity index 93% rename from src/commands/subcommands/blacklist/list.ts rename to src/commands/slash/Main/blacklist/list.ts index 51162e03..b967b99f 100644 --- a/src/commands/subcommands/blacklist/list.ts +++ b/src/commands/slash/Main/blacklist/list.ts @@ -1,9 +1,9 @@ import { APIEmbedField, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import db from '../../../utils/Db.js'; -import BlacklistCommand from '../../slash/Main/blacklist.js'; +import db from '../../../../utils/Db.js'; +import BlacklistCommand from './index.js'; import { stripIndents } from 'common-tags'; -import { paginate } from '../../../utils/Pagination.js'; -import { colors } from '../../../utils/Constants.js'; +import { paginate } from '../../../../utils/Pagination.js'; +import { colors } from '../../../../utils/Constants.js'; export default class ListBlacklists extends BlacklistCommand { async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/commands/subcommands/blacklist/server.ts b/src/commands/slash/Main/blacklist/server.ts similarity index 93% rename from src/commands/subcommands/blacklist/server.ts rename to src/commands/slash/Main/blacklist/server.ts index 37824d40..3cf0bf17 100644 --- a/src/commands/subcommands/blacklist/server.ts +++ b/src/commands/slash/Main/blacklist/server.ts @@ -1,10 +1,10 @@ import { captureException } from '@sentry/node'; import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { emojis } from '../../../utils/Constants.js'; -import db from '../../../utils/Db.js'; -import BlacklistCommand from '../../slash/Main/blacklist.js'; -import Logger from '../../../utils/Logger.js'; -import BlacklistManager from '../../../structures/BlacklistManager.js'; +import { emojis } from '../../../../utils/Constants.js'; +import db from '../../../../utils/Db.js'; +import BlacklistCommand from './index.js'; +import Logger from '../../../../utils/Logger.js'; +import BlacklistManager from '../../../../structures/BlacklistManager.js'; import parse from 'parse-duration'; export default class UserBlacklist extends BlacklistCommand { diff --git a/src/commands/subcommands/blacklist/user.ts b/src/commands/slash/Main/blacklist/user.ts similarity index 94% rename from src/commands/subcommands/blacklist/user.ts rename to src/commands/slash/Main/blacklist/user.ts index 7419adc8..c7328be6 100644 --- a/src/commands/subcommands/blacklist/user.ts +++ b/src/commands/slash/Main/blacklist/user.ts @@ -1,9 +1,9 @@ import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import db from '../../../utils/Db.js'; -import BlacklistCommand from '../../slash/Main/blacklist.js'; -import BlacklistManager from '../../../structures/BlacklistManager.js'; -import { emojis } from '../../../utils/Constants.js'; +import db from '../../../../utils/Db.js'; +import BlacklistCommand from './index.js'; +import BlacklistManager from '../../../../structures/BlacklistManager.js'; import parse from 'parse-duration'; +import { emojis } from '../../../../utils/Constants.js'; export default class Server extends BlacklistCommand { async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/commands/slash/Main/hub.ts b/src/commands/slash/Main/hub.ts deleted file mode 100644 index 4a46ad9e..00000000 --- a/src/commands/slash/Main/hub.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { - APIApplicationCommandBasicOption, - ApplicationCommandOptionType, - AutocompleteInteraction, - ChannelType, - ChatInputCommandInteraction, - Collection, - RESTPostAPIApplicationCommandsJSONBody, -} from 'discord.js'; -import BaseCommand from '../../BaseCommand.js'; -import db from '../../../utils/Db.js'; - -const hubOption: APIApplicationCommandBasicOption = { - type: ApplicationCommandOptionType.String, - name: 'hub', - description: 'Choose a hub.', - required: true, - autocomplete: true, -}; - -export default class Hub extends BaseCommand { - readonly data: RESTPostAPIApplicationCommandsJSONBody = { - name: 'hub', - description: 'Manage your hubs.', - dm_permission: false, - options: [ - { - type: ApplicationCommandOptionType.Subcommand, - name: 'browse', - description: '🔍 Browse public hubs and join them!', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'hub', - description: 'Search for a hub.', - required: false, - autocomplete: true, - }, - { - type: ApplicationCommandOptionType.String, - name: 'sort', - description: 'Sort the results.', - required: false, - choices: [ - { - name: 'Most Active', - value: 'most-active', - }, - ], - }, - ], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'manage', - description: '📝 Edit a hub you own.', - options: [hubOption], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'settings', - description: '⚙️ Edit your hub settings', - options: [hubOption], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'join', - description: '🔗 Join a public/private hub from this server.', - options: [ - { - type: ApplicationCommandOptionType.Channel, - name: 'channel', - description: 'The channel you want to use connect to a hub.', - required: true, - channel_types: [ChannelType.GuildText, ChannelType.PublicThread, ChannelType.PrivateThread], - }, - { ...hubOption, required: false }, - { - type: ApplicationCommandOptionType.String, - name: 'invite', - description: 'The invite code of the private hub you want to join.', - required: false, - }, - ], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'leave', - description: '👋 Leave a hub from this server.', - options: [hubOption], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'create', - description: '✨ Create a new hub.', - options: [ - { - name: 'name', - description: 'A name for your hub.', - required: true, - type: ApplicationCommandOptionType.String, - }, - { - name: 'icon', - description: 'The icon of your hub. Use a valid i.imgur.com link.', - type: ApplicationCommandOptionType.String, - }, - { - name: 'banner', - description: 'The banner of your hub. Use a valid i.imgur.com link.', - type: ApplicationCommandOptionType.String, - }, - ], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'delete', - description: '🗑️ Delete a hub you own.', - options: [hubOption], - }, - { - type: ApplicationCommandOptionType.Subcommand, - name: 'connections', - description: '📜 List all connected servers to your hub.', - options: [ - { - type: ApplicationCommandOptionType.String, - name: 'hub', - description: 'Choose a hub.', - required: true, - autocomplete: true, - }, - ], - }, - ], - }; - - // subcommand classes are added to this map in their respective files - static readonly subcommands = new Collection; - - async execute(interaction: ChatInputCommandInteraction): Promise { - const subCommand = interaction.options.getSubcommand(); - const isValid = Hub.subcommands?.get(subCommand); - if (isValid) return await isValid.execute(interaction); - } - - async autocomplete(interaction: AutocompleteInteraction): Promise { - const modCmds = ['manage', 'settings', 'connections', 'invite', 'moderator']; - - const subcommand = interaction.options.getSubcommand(); - const subcommandGroup = interaction.options.getSubcommandGroup(); - const focusedValue = interaction.options.getFocused(); - let hubChoices; - - if (subcommand === 'browse' || subcommand === 'join') { - hubChoices = await db.hubs.findMany({ - where: { - name: { mode: 'insensitive', contains: focusedValue }, - private: false, - }, - take: 25, - }); - } - else if (modCmds.includes(subcommandGroup || subcommand)) { - hubChoices = await db.hubs.findMany({ - where: { - name: { mode: 'insensitive', contains: focusedValue }, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, - take: 25, - }); - } - else if (subcommand === 'leave') { - const networks = await db.connectedList.findMany({ - where: { serverId: interaction.guild?.id }, - select: { channelId: true, hub: true }, - take: 25, - }); - - const filteredNets = networks - .filter((network) => network.hub?.name.toLowerCase().includes(focusedValue.toLowerCase())) - .map(async (network) => { - const channel = await interaction.guild?.channels - .fetch(network.channelId) - .catch(() => null); - return { - name: `${network.hub?.name} | #${channel?.name ?? network.channelId}`, - value: network.channelId, - }; - }); - - return await interaction.respond(await Promise.all(filteredNets)); - } - else if (subcommand === 'delete') { - hubChoices = await db.hubs.findMany({ - where: { - ownerId: interaction.user.id, - name: { mode: 'insensitive', contains: focusedValue }, - }, - take: 25, - }); - } - - const filtered = hubChoices?.map((hub) => ({ name: hub.name, value: hub.name })); - filtered ? await interaction.respond(filtered) : null; - } -} diff --git a/src/commands/subcommands/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts similarity index 96% rename from src/commands/subcommands/hub/browse.ts rename to src/commands/slash/Main/hub/browse.ts index 83ad4d7e..a7148c13 100644 --- a/src/commands/subcommands/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -14,15 +14,15 @@ import { ModalSubmitInteraction, ChannelSelectMenuInteraction, } from 'discord.js'; -import db from '../../../utils/Db.js'; -import Hub from '../../slash/Main/hub.js'; +import db from '../../../../utils/Db.js'; +import Hub from './index.js'; import { hubs } from '@prisma/client'; -import { emojis } from '../../../utils/Constants.js'; -import { paginate } from '../../../utils/Pagination.js'; -import { calculateAverageRating, getOrCreateWebhook } from '../../../utils/Utils.js'; -import { showOnboarding } from '../../../scripts/network/onboarding.js'; -import { CustomID } from '../../../structures/CustomID.js'; -import { Interaction } from '../../../decorators/Interaction.js'; +import { emojis } from '../../../../utils/Constants.js'; +import { paginate } from '../../../../utils/Pagination.js'; +import { calculateAverageRating, getOrCreateWebhook } from '../../../../utils/Utils.js'; +import { showOnboarding } from '../../../../scripts/network/onboarding.js'; +import { CustomID } from '../../../../structures/CustomID.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; import { stripIndents } from 'common-tags'; export default class Browse extends Hub { diff --git a/src/commands/subcommands/hub/connections.ts b/src/commands/slash/Main/hub/connections.ts similarity index 92% rename from src/commands/subcommands/hub/connections.ts rename to src/commands/slash/Main/hub/connections.ts index 61f2a2e7..4024f737 100644 --- a/src/commands/subcommands/hub/connections.ts +++ b/src/commands/slash/Main/hub/connections.ts @@ -1,9 +1,9 @@ import { ChatInputCommandInteraction, CacheType, EmbedBuilder } from 'discord.js'; -import Hub from '../../slash/Main/hub.js'; +import Hub from './index.js'; import { stripIndent } from 'common-tags'; -import { emojis } from '../../../utils/Constants.js'; -import { paginate } from '../../../utils/Pagination.js'; -import db from '../../../utils/Db.js'; +import { emojis } from '../../../../utils/Constants.js'; +import { paginate } from '../../../../utils/Pagination.js'; +import db from '../../../../utils/Db.js'; export default class Connections extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { diff --git a/src/commands/subcommands/hub/create.ts b/src/commands/slash/Main/hub/create.ts similarity index 93% rename from src/commands/subcommands/hub/create.ts rename to src/commands/slash/Main/hub/create.ts index bb258fb6..3f132f26 100644 --- a/src/commands/subcommands/hub/create.ts +++ b/src/commands/slash/Main/hub/create.ts @@ -8,13 +8,13 @@ import { TextInputStyle, ModalSubmitInteraction, } from 'discord.js'; -import Hub from '../../slash/Main/hub.js'; -import db from '../../../utils/Db.js'; +import Hub from './index.js'; +import db from '../../../../utils/Db.js'; import { stripIndents } from 'common-tags'; -import { Interaction } from '../../../decorators/Interaction.js'; -import { HubSettingsBits } from '../../../utils/BitFields.js'; -import { checkAndFetchImgurUrl, errorEmbed } from '../../../utils/Utils.js'; -import { emojis } from '../../../utils/Constants.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; +import { HubSettingsBits } from '../../../../utils/BitFields.js'; +import { checkAndFetchImgurUrl, errorEmbed } from '../../../../utils/Utils.js'; +import { emojis } from '../../../../utils/Constants.js'; export default class Create extends Hub { // TODO: readonly cooldown = 60 * 60 * 1000; @@ -126,12 +126,12 @@ export default class Create extends Hub { await db.hubs.create({ data: { - name: name, + name, description, private: true, ownerId: interaction.user.id, iconUrl: iconUrl ?? interaction.client.user.displayAvatarURL(), - bannerUrl: bannerUrl || undefined, + bannerUrl, settings: HubSettingsBits.SpamFilter | HubSettingsBits.Reactions | HubSettingsBits.BlockNSFW, }, diff --git a/src/commands/subcommands/hub/delete.ts b/src/commands/slash/Main/hub/delete.ts similarity index 90% rename from src/commands/subcommands/hub/delete.ts rename to src/commands/slash/Main/hub/delete.ts index 4093acc9..bd9b52da 100644 --- a/src/commands/subcommands/hub/delete.ts +++ b/src/commands/slash/Main/hub/delete.ts @@ -7,13 +7,13 @@ import { EmbedBuilder, ButtonInteraction, } from 'discord.js'; -import db from '../../../utils/Db.js'; -import Hub from '../../slash/Main/hub.js'; +import db from '../../../../utils/Db.js'; +import Hub from './index.js'; import { captureException } from '@sentry/node'; -import { emojis } from '../../../utils/Constants.js'; -import { deleteHubs, setComponentExpiry } from '../../../utils/Utils.js'; -import { CustomID } from '../../../structures/CustomID.js'; -import { Interaction } from '../../../decorators/Interaction.js'; +import { emojis } from '../../../../utils/Constants.js'; +import { deleteHubs, setComponentExpiry } from '../../../../utils/Utils.js'; +import { CustomID } from '../../../../structures/CustomID.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; export default class Delete extends Hub { async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts new file mode 100644 index 00000000..5f423986 --- /dev/null +++ b/src/commands/slash/Main/hub/index.ts @@ -0,0 +1,371 @@ +import { + APIApplicationCommandBasicOption, + ApplicationCommandOptionType, + AutocompleteInteraction, + ChannelType, + ChatInputCommandInteraction, + Collection, + RESTPostAPIApplicationCommandsJSONBody, +} from 'discord.js'; +import BaseCommand from '../../../BaseCommand.js'; +import db from '../../../../utils/Db.js'; + +const hubOption: APIApplicationCommandBasicOption = { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'Choose a hub.', + required: true, + autocomplete: true, +}; + +export default class Hub extends BaseCommand { + readonly data: RESTPostAPIApplicationCommandsJSONBody = { + name: 'hub', + description: 'Manage your hubs.', + dm_permission: false, + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'browse', + description: '🔍 Browse public hubs and join them!', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'Search for a hub.', + required: false, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'sort', + description: 'Sort the results.', + required: false, + choices: [ + { + name: 'Most Active', + value: 'most-active', + }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'manage', + description: '📝 Edit a hub you own.', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'settings', + description: '⚙️ Edit your hub settings', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'join', + description: '🔗 Join a public/private hub from this server.', + options: [ + { + type: ApplicationCommandOptionType.Channel, + name: 'channel', + description: 'The channel you want to use connect to a hub.', + required: true, + channel_types: [ + ChannelType.GuildText, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ], + }, + { ...hubOption, required: false }, + { + type: ApplicationCommandOptionType.String, + name: 'invite', + description: 'The invite code of the private hub you want to join.', + required: false, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'leave', + description: '👋 Leave a hub from this server.', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'create', + description: '✨ Create a new hub.', + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'delete', + description: '🗑️ Delete a hub you own.', + options: [hubOption], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'connections', + description: '📜 List all connected servers to your hub.', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'Choose a hub.', + required: true, + autocomplete: true, + }, + ], + }, + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: 'moderator', + description: 'Manage hub moderators', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'add', + description: 'Add a new hub moderator', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub you wish to add moderators to', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.User, + name: 'user', + description: 'User who will become hub moderator', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'position', + description: 'Determines what hub permissions they have.', + required: false, + choices: [ + { name: 'Network Moderator', value: 'network_mod' }, + { name: 'Hub Manager', value: 'manager' }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'remove', + description: 'Remove a user from moderator position in your hub', + options: [ + { ...hubOption }, + { + type: ApplicationCommandOptionType.User, + name: 'user', + description: 'The user who should be removed', + required: true, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'update', + description: 'Update the position of a hub moderator', + options: [ + { ...hubOption }, + { + type: ApplicationCommandOptionType.User, + name: 'user', + description: 'The moderator you wish the change', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'position', + description: 'The moderator position to update', + required: true, + choices: [ + { name: 'Network Moderator', value: 'network_mod' }, + { name: 'Hub Manager', value: 'manager' }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'list', + description: 'List all moderators on a hub', + options: [{ ...hubOption }], + }, + ], + }, + ], + /* + .addSubcommandGroup((subcommandGroup) => + subcommandGroup + .setName('moderator') + .setDescription('Manage hub moderators') + .addSubcommand((subcommand) => + subcommand + .setName('add') + .setDescription('Add a new hub moderator') + .addStringOption(stringOpt => + stringOpt + .setName('hub') + .setDescription('The name of the hub you wish to add moderators to') + .setAutocomplete(true) + .setRequired(true), + ) + .addUserOption(stringOpt => + stringOpt + .setName('user') + .setDescription('User who will become hub moderator') + .setRequired(true), + ) + .addStringOption(stringOpt => + stringOpt + .setName('role') + .setDescription('Determines what hub permissions they have') + .addChoices( + { name: 'Network Moderator', value: 'network_mod' }, + { name: 'Hub Manager', value: 'manager' }, + ) + .setRequired(false), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove a user from moderator position in your hub') + .addStringOption(stringOpt => + stringOpt + .setName('hub') + .setDescription('The name of the hub you wish to add moderators to') + .setAutocomplete(true) + .setRequired(true), + ) + .addUserOption(userOpt => + userOpt + .setName('user') + .setDescription('The user who should be removed') + .setRequired(true), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName('update') + .setDescription('Update the role of a hub moderator') + .addStringOption(stringOpt => + stringOpt + .setName('hub') + .setDescription('The name of the hub') + .setAutocomplete(true) + .setRequired(true), + ) + .addUserOption(userOpt => + userOpt + .setName('user') + .setDescription('The moderator you wish the change') + .setRequired(true), + ) + .addStringOption(stringOpt => + stringOpt + .setName('role') + .setDescription('The moderator role to update') + .setRequired(true) + .addChoices( + { name: 'Network Moderator', value: 'network_mod' }, + { name: 'Hub Manager', value: 'manager' }, + ), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName('list') + .setDescription('List all moderators on a hub') + .addStringOption(stringOpt => + stringOpt + .setName('hub') + .setDescription('The name of the hub') + .setAutocomplete(true) + .setRequired(true), + ), + ), + ) + */ + }; + + // subcommand classes are added to this map in their respective files + static readonly subcommands = new Collection(); + + async execute(interaction: ChatInputCommandInteraction): Promise { + const subCommand = + interaction.options.getSubcommandGroup() || interaction.options.getSubcommand(); + const isValid = Hub.subcommands?.get(subCommand); + if (isValid) return await isValid.execute(interaction); + } + + async autocomplete(interaction: AutocompleteInteraction): Promise { + const modCmds = ['manage', 'settings', 'connections', 'invite', 'moderator']; + + const subcommand = interaction.options.getSubcommand(); + const subcommandGroup = interaction.options.getSubcommandGroup(); + const focusedValue = interaction.options.getFocused(); + let hubChoices; + + if (subcommand === 'browse' || subcommand === 'join') { + hubChoices = await db.hubs.findMany({ + where: { + name: { mode: 'insensitive', contains: focusedValue }, + private: false, + }, + take: 25, + }); + } + else if (modCmds.includes(subcommandGroup || subcommand)) { + hubChoices = await db.hubs.findMany({ + where: { + name: { mode: 'insensitive', contains: focusedValue }, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, + take: 25, + }); + } + else if (subcommand === 'leave') { + const networks = await db.connectedList.findMany({ + where: { serverId: interaction.guild?.id }, + select: { channelId: true, hub: true }, + take: 25, + }); + + const filteredNets = networks + .filter((network) => network.hub?.name.toLowerCase().includes(focusedValue.toLowerCase())) + .map(async (network) => { + const channel = await interaction.guild?.channels + .fetch(network.channelId) + .catch(() => null); + return { + name: `${network.hub?.name} | #${channel?.name ?? network.channelId}`, + value: network.channelId, + }; + }); + + return await interaction.respond(await Promise.all(filteredNets)); + } + else if (subcommand === 'delete') { + hubChoices = await db.hubs.findMany({ + where: { + ownerId: interaction.user.id, + name: { mode: 'insensitive', contains: focusedValue }, + }, + take: 25, + }); + } + + const filtered = hubChoices?.map((hub) => ({ name: hub.name, value: hub.name })); + filtered ? await interaction.respond(filtered) : null; + } +} diff --git a/src/commands/slash/Main/hub/invite.ts b/src/commands/slash/Main/hub/invite.ts new file mode 100644 index 00000000..6a937945 --- /dev/null +++ b/src/commands/slash/Main/hub/invite.ts @@ -0,0 +1,164 @@ +import { ChatInputCommandInteraction, CacheType, EmbedBuilder } from 'discord.js'; +import Hub from './index.js'; +import { captureException } from '@sentry/node'; +import { stripIndents } from 'common-tags'; +import { emojis } from '../../../../utils/Constants.js'; +import db from '../../../../utils/Db.js'; +import Logger from '../../../../utils/Logger.js'; + +export default class Invite extends Hub { + async execute(interaction: ChatInputCommandInteraction) { + const subcommand = interaction.options.getSubcommand(); + + switch (subcommand) { + case 'create': { + const expires = new Date(); + const hubName = interaction.options.getString('hub', true); + const hours = interaction.options.getNumber('expiry'); + hours + ? expires.setHours(expires.getHours() + hours) + : expires.setHours(expires.getHours() + 24); + + const hubInDb = await db.hubs.findFirst({ + where: { + name: hubName, + private: true, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + }); + + if (!hubInDb) { + await interaction.reply({ + content: `${emojis.no} Invalid Hub Provided. Make sure you are a owner/manager of the hub and that hub is private.`, + ephemeral: true, + }); + return; + } + const createdInvite = await db.hubInvites.create({ + data: { + expires, + hub: { connect: { name: hubName } }, + }, + }); + + const embed = new EmbedBuilder() + .setTitle('Invite Created') + .setDescription( + stripIndents` + Give this code to someone who wishes to join the hub. This invite has unlimited uses. + + Join using: \`/hub join invite:${createdInvite.code}\` + + **Code:** \`${createdInvite.code}\` + **Expiry ** + `, + ) + .setColor('Green') + .setTimestamp(); + + await interaction.reply({ + embeds: [embed], + ephemeral: true, + }); + break; + } + + case 'revoke': { + const code = interaction.options.getString('code', true); + const inviteInDb = await db.hubInvites.findFirst({ + where: { + code, + hub: { + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + }, + }); + + if (!inviteInDb) { + await interaction.reply({ + content: `${emojis.no} Invalid Invite Code.`, + ephemeral: true, + }); + return; + } + + try { + await db.hubInvites.delete({ where: { code } }); + await interaction.reply({ + content: `Successfully revoked invite \`${code}\`!`, + ephemeral: true, + }); + } + catch (e) { + Logger.error(e); + captureException(e); + await interaction + .reply({ + content: + 'An error occoured while trying to revoke invite! The developers have been notified.', + ephemeral: true, + }) + .catch(() => null); + return; + } + break; + } + + case 'list': { + const hubName = interaction.options.getString('hub', true); + const hubInDb = await db.hubs.findFirst({ + where: { + name: hubName, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + }); + + if (!hubInDb?.private) { + await interaction.reply({ + content: 'You can only view invite codes for private hubs.', + ephemeral: true, + }); + return; + } + + const invitesInDb = await db.hubInvites.findMany({ where: { hubId: hubInDb.id } }); + if (invitesInDb.length === 0) { + await interaction.reply({ + content: `${emojis.yes} There are no invites to this hub yet.`, + ephemeral: true, + }); + return; + } + + const inviteArr = invitesInDb.map( + (inv, index) => + `${index + 1}. \`${inv.code}\` - `, + ); + + const inviteEmbed = new EmbedBuilder() + .setTitle('Invite Codes') + .setDescription(inviteArr.join('\n')) + .setColor('Yellow') + .setTimestamp(); + + await interaction.reply({ + embeds: [inviteEmbed], + ephemeral: true, + }); + break; + } + + default: + break; + } + } +} diff --git a/src/commands/subcommands/hub/join.ts b/src/commands/slash/Main/hub/join.ts similarity index 92% rename from src/commands/subcommands/hub/join.ts rename to src/commands/slash/Main/hub/join.ts index 74a455f2..ed91ac6c 100644 --- a/src/commands/subcommands/hub/join.ts +++ b/src/commands/slash/Main/hub/join.ts @@ -1,11 +1,11 @@ import { ChannelType, ChatInputCommandInteraction } from 'discord.js'; -import { emojis } from '../../../utils/Constants.js'; -import Hub from '../../slash/Main/hub.js'; -import db from '../../../utils/Db.js'; -import BlacklistManager from '../../../structures/BlacklistManager.js'; +import { emojis } from '../../../../utils/Constants.js'; +import Hub from './index.js'; +import db from '../../../../utils/Db.js'; +import BlacklistManager from '../../../../structures/BlacklistManager.js'; import { hubs } from '@prisma/client'; -import { getOrCreateWebhook } from '../../../utils/Utils.js'; -import { showOnboarding } from '../../../scripts/network/onboarding.js'; +import { getOrCreateWebhook } from '../../../../utils/Utils.js'; +import { showOnboarding } from '../../../../scripts/network/onboarding.js'; import { stripIndents } from 'common-tags'; export default class JoinSubCommand extends Hub { diff --git a/src/commands/subcommands/hub/leave.ts b/src/commands/slash/Main/hub/leave.ts similarity index 86% rename from src/commands/subcommands/hub/leave.ts rename to src/commands/slash/Main/hub/leave.ts index 65b9a1cf..91eec668 100644 --- a/src/commands/subcommands/hub/leave.ts +++ b/src/commands/slash/Main/hub/leave.ts @@ -7,12 +7,12 @@ import { ButtonStyle, EmbedBuilder, } from 'discord.js'; -import Hub from '../../slash/Main/hub.js'; -import { Interaction } from '../../../decorators/Interaction.js'; -import { CustomID } from '../../../structures/CustomID.js'; -import { emojis } from '../../../utils/Constants.js'; -import db from '../../../utils/Db.js'; -import { setComponentExpiry } from '../../../utils/Utils.js'; +import Hub from './index.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; +import { CustomID } from '../../../../structures/CustomID.js'; +import { emojis } from '../../../../utils/Constants.js'; +import db from '../../../../utils/Db.js'; +import { setComponentExpiry } from '../../../../utils/Utils.js'; export default class Leave extends Hub { async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/commands/subcommands/hub/manage.ts b/src/commands/slash/Main/hub/manage.ts similarity index 96% rename from src/commands/subcommands/hub/manage.ts rename to src/commands/slash/Main/hub/manage.ts index d267cba0..93507bca 100644 --- a/src/commands/subcommands/hub/manage.ts +++ b/src/commands/slash/Main/hub/manage.ts @@ -10,14 +10,14 @@ import { TextInputBuilder, TextInputStyle, } from 'discord.js'; -import db from '../../../utils/Db.js'; -import Hub from '../../slash/Main/hub.js'; +import db from '../../../../utils/Db.js'; +import Hub from './index.js'; import { hubs, connectedList } from '@prisma/client'; import { stripIndents } from 'common-tags'; -import { emojis } from '../../../utils/Constants.js'; -import { Interaction } from '../../../decorators/Interaction.js'; -import { CustomID } from '../../../structures/CustomID.js'; -import { errorEmbed, setComponentExpiry } from '../../../utils/Utils.js'; +import { emojis } from '../../../../utils/Constants.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; +import { CustomID } from '../../../../structures/CustomID.js'; +import { errorEmbed, setComponentExpiry } from '../../../../utils/Utils.js'; export default class Manage extends Hub { async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/commands/slash/Main/hub/moderator.ts b/src/commands/slash/Main/hub/moderator.ts new file mode 100644 index 00000000..a90f806e --- /dev/null +++ b/src/commands/slash/Main/hub/moderator.ts @@ -0,0 +1,133 @@ +import { ChatInputCommandInteraction, CacheType, EmbedBuilder } from 'discord.js'; +import Hub from './index.js'; +import db from '../../../../utils/Db.js'; + +export default class Moderator extends Hub { + async execute(interaction: ChatInputCommandInteraction) { + const hubName = interaction.options.getString('hub', true); + const hub = await db.hubs.findFirst({ + where: { + name: hubName, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + }); + + if (!hub) { + return await interaction.reply({ + content: 'Invalid hub input. Make sure the hub exists and that you are a owner/manager of the hub.', + ephemeral: true, + }); + } + + switch (interaction.options.getSubcommand()) { + case 'add': { + const user = interaction.options.getUser('user', true); + + if (hub.moderators.find((mod) => mod.userId === user.id)) { + return interaction.reply({ + content: `User ${user} is already a moderator for **${hub.name}**!`, + ephemeral: true, + }); + } + + const position = interaction.options.getString('role') ?? 'network_mod'; + await db.hubs.update({ + where: { id: hub.id }, + data: { moderators: { push: { userId: user.id, position } } }, + }); + interaction.reply(`Added ${user} as a hub moderator for **${hub.name}**!`); + break; + } + + case 'remove': { + const user = interaction.options.getUser('user', true); + + if (!hub.moderators.find((mod) => mod.userId === user.id)) { + return interaction.reply({ + content: `User ${user} is not a moderator for **${hub.name}**!`, + ephemeral: true, + }); + } + + if (hub.ownerId !== interaction.user.id) { + if (user.id === interaction.user.id) { + return interaction.reply({ + content: 'I don\'t know why you would want to do that, but only the owner of the hub can remove you!', + ephemeral: true, + }); + } + + if (hub.moderators.find(m => m.position === 'manager' && m.userId === user.id)) { + return interaction.reply({ + content: 'Only the owner of the hub can remove a manager!', + ephemeral: true, + }); + } + } + await db.hubs.update({ + where: { id: hub.id }, + data: { + moderators: { deleteMany: { where: { userId: user.id } } }, + }, + }); + interaction.reply(`Removed hub moderator ${user} from **${hub.name}**!`); + break; + } + + case 'update': { + const user = interaction.options.getUser('user', true); + const position = interaction.options.getString('role', true); + + if (!hub.moderators.find((mod) => mod.userId === user.id)) { + return interaction.reply({ + content: `User ${user} is not a moderator for **${hub.name}**!`, + ephemeral: true, + }); + } + + if (hub.ownerId !== interaction.user.id && user.id === interaction.user.id) { + return interaction.reply({ + content: 'Only the owner of the hub can update your role!', + ephemeral: true, + }); + } + + await db.hubs.update({ + where: { id: hub.id }, + data: { + moderators: { + updateMany: { where: { userId: user.id }, data: { position } }, + }, + }, + }); + interaction.reply(`Sucessfully moved ${user} to the role of \`${position}\` for **${hub.name}**!`); + break; + } + + case 'list': { + await interaction.reply({ + embeds: [ + new EmbedBuilder() + .setTitle('Hub Moderators') + .setDescription( + hub.moderators.length > 0 + ? hub.moderators + .map((mod, index) => `${index + 1}. <@${mod.userId}> - ${mod.position === 'network_mod' ? 'Network Moderator' : 'Hub Manager'}`) + .join('\n') + : 'There are no moderators for this hub yet.', + ) + .setColor('Aqua') + .setTimestamp(), + ], + ephemeral: true, + }); + break; + } + default: + break; + } + } +} \ No newline at end of file diff --git a/src/commands/subcommands/hub/settings.ts b/src/commands/slash/Main/hub/settings.ts similarity index 92% rename from src/commands/subcommands/hub/settings.ts rename to src/commands/slash/Main/hub/settings.ts index 542ea0f9..cfe0db10 100644 --- a/src/commands/subcommands/hub/settings.ts +++ b/src/commands/slash/Main/hub/settings.ts @@ -6,15 +6,15 @@ import { StringSelectMenuBuilder, Snowflake, } from 'discord.js'; -import db from '../../../utils/Db.js'; -import Hub from '../../slash/Main/hub.js'; +import db from '../../../../utils/Db.js'; +import Hub from './index.js'; import { hubs } from '@prisma/client'; -import { HubSettingsBitField, HubSettingsString } from '../../../utils/BitFields.js'; -import { colors, emojis } from '../../../utils/Constants.js'; -import { Interaction } from '../../../decorators/Interaction.js'; -import { CustomID } from '../../../structures/CustomID.js'; +import { HubSettingsBitField, HubSettingsString } from '../../../../utils/BitFields.js'; +import { colors, emojis } from '../../../../utils/Constants.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; +import { CustomID } from '../../../../structures/CustomID.js'; import { StringSelectMenuInteraction } from 'discord.js'; -import { errorEmbed } from '../../../utils/Utils.js'; +import { errorEmbed } from '../../../../utils/Utils.js'; export default class Settings extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { diff --git a/src/commands/slash/Support/support.ts b/src/commands/slash/Support/support/index.ts similarity index 96% rename from src/commands/slash/Support/support.ts rename to src/commands/slash/Support/support/index.ts index 96d51c42..0205873d 100644 --- a/src/commands/slash/Support/support.ts +++ b/src/commands/slash/Support/support/index.ts @@ -5,7 +5,7 @@ import { Collection, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; -import BaseCommand from '../../BaseCommand.js'; +import BaseCommand from '../../../BaseCommand.js'; export default class Support extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { diff --git a/src/commands/subcommands/support/report.ts b/src/commands/slash/Support/support/report.ts similarity index 91% rename from src/commands/subcommands/support/report.ts rename to src/commands/slash/Support/support/report.ts index 9734c82c..58a35f3d 100644 --- a/src/commands/subcommands/support/report.ts +++ b/src/commands/slash/Support/support/report.ts @@ -14,13 +14,13 @@ import { ThreadChannel, } from 'discord.js'; import { stripIndents } from 'common-tags'; -import { channels, colors, emojis } from '../../../utils/Constants.js'; -import Support from '../../slash/Support/support.js'; -import { CustomID } from '../../../structures/CustomID.js'; -import { Interaction } from '../../../decorators/Interaction.js'; +import { channels, colors, emojis } from '../../../../utils/Constants.js'; +import Support from './index.js'; +import { CustomID } from '../../../../structures/CustomID.js'; +import { Interaction } from '../../../../decorators/Interaction.js'; export default class Report extends Support { - readonly reportModal = new ModalBuilder() + static readonly reportModal = new ModalBuilder() .setTitle('New Report') .setCustomId(new CustomID().setIdentifier('report_modal').toString()) .addComponents( @@ -75,7 +75,7 @@ export default class Report extends Support { }); } else if (reportType === 'server' || reportType === 'user' || reportType === 'other') { - const modal = new ModalBuilder(this.reportModal) + const modal = new ModalBuilder(Report.reportModal) .setCustomId(new CustomID().setIdentifier('report_modal', reportType).toString()) .addComponents( new ActionRowBuilder().addComponents( @@ -96,13 +96,14 @@ export default class Report extends Support { @Interaction('report') async handleComponents(interaction: MessageComponentInteraction) { if (interaction.isStringSelectMenu()) { - const modal = new ModalBuilder(this.reportModal) + const modal = new ModalBuilder() .setCustomId( new CustomID() .setIdentifier('report_modal', 'bug') .addArgs(interaction.values.join(', ')) .toString(), ) + .setTitle('New Bug Report') .setComponents( new ActionRowBuilder().addComponents( new TextInputBuilder() @@ -199,7 +200,7 @@ export default class Report extends Support { content: stripIndents` ${emojis.no} I couldn't find a user with that ID.\n\n **To find a user's ID within the network, please follow these instructions:** - ${emojis.dotYellow} Right click on a message sent from the user in question select \`Apps > User Info\`. Please double-check the ID and try again. + ${emojis.dotYellow} Right click on a message sent from the user in question select \`Apps > Message Info\`. Please double-check the ID and try again. `, ephemeral: true, }); @@ -228,7 +229,7 @@ export default class Report extends Support { content: stripIndents` ${emojis.no} I couldn't find a server with that ID.\n **To find a server ID within the network, please follow these instructions:** - ${emojis.dotYellow} Right click on a message sent by the server in question and select \`Apps > Server Info\`. Please double-check the ID and try again. + ${emojis.dotYellow} Right click on a message sent by the server in question and select \`Apps > Message Info\`. Please double-check the ID and try again. `, ephemeral: true, }); @@ -238,9 +239,13 @@ export default class Report extends Support { const serverReport = new EmbedBuilder() .setColor('Red') .setTitle('New Server Report') - .setDescription(`Server Name: ${reportedServer.name}\nServer Id: ${reportedServer.members}`) + .setDescription( + `Server Name: ${reportedServer.name}\nServer Id: ${reportedServer.members}`, + ) .setFields({ name: 'Reason for report', value: reportDescription }) - .setThumbnail(`https://cdn.discordapp.com/icons/${reportedServer.id}/${reportedServer.icon}.png?size=2048`) + .setThumbnail( + `https://cdn.discordapp.com/icons/${reportedServer.id}/${reportedServer.icon}.png?size=2048`, + ) .setFooter({ text: `Reported by ${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() || interaction.user.defaultAvatarURL, diff --git a/src/commands/subcommands/support/server.ts b/src/commands/slash/Support/support/server.ts similarity index 80% rename from src/commands/subcommands/support/server.ts rename to src/commands/slash/Support/support/server.ts index b871ce4d..1b93e271 100644 --- a/src/commands/subcommands/support/server.ts +++ b/src/commands/slash/Support/support/server.ts @@ -1,6 +1,6 @@ import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { colors } from '../../../utils/Constants.js'; -import Support from '../../slash/Support/support.js'; +import { colors } from '../../../../utils/Constants.js'; +import Support from './index.js'; export default class SupportServer extends Support { async execute(interaction: ChatInputCommandInteraction) { diff --git a/src/structures/CommandManager.ts b/src/structures/CommandManager.ts index bb531f91..dd83baaa 100644 --- a/src/structures/CommandManager.ts +++ b/src/structures/CommandManager.ts @@ -1,8 +1,8 @@ -import fs from 'fs'; -import path from 'path'; +import { access, constants, readdirSync, statSync } from 'fs'; +import { join, dirname } from 'path'; import Factory from '../Factory.js'; import Logger from '../utils/Logger.js'; -import BaseCommand from '../commands/BaseCommand.js'; +import BaseCommand, { commandsMap } from '../commands/BaseCommand.js'; import { emojis } from '../utils/Constants.js'; import { CustomID } from './CustomID.js'; import { Interaction } from 'discord.js'; @@ -10,7 +10,7 @@ import { captureException } from '@sentry/node'; import { errorEmbed } from '../utils/Utils.js'; const __filename = new URL(import.meta.url).pathname; -const __dirname = path.dirname(__filename); +const __dirname = dirname(__filename); export default class CommandManager extends Factory { public get commandsMap() { @@ -70,27 +70,40 @@ export default class CommandManager extends Factory { * Loads all commands from the Commands directory * Commands are automatically added to the `clientCommands` map */ - static async loadCommandFiles( - commandDir = path.join(__dirname, '..', 'commands'), - ): Promise { - const files = fs.readdirSync(commandDir); + static async loadCommandFiles(commandDir = join(__dirname, '..', 'commands')): Promise { + const files = readdirSync(commandDir); for (const file of files) { - const filePath = path.join(commandDir, file); - const stats = fs.statSync(filePath); + const filePath = join(commandDir, file); + const stats = statSync(filePath); - if (stats.isDirectory() && file !== 'subcommands') { - // If the item is a directory, recursively read its files + // If the item is a directory, recursively read its files + if (stats.isDirectory()) { await this.loadCommandFiles(filePath); } // If the item is a .js file, read its contents else if (file.endsWith('.js') && file !== 'BaseCommand.js') { - // initializing it will automatically add the command to the clientCommands map const imported = await import(filePath); - const command = new imported.default() as BaseCommand; - command.loadCommand(); - command.loadSubcommands(); + const command = new imported.default(); + + // if the command extends BaseCommand (ie. its not a subcommand), add it to the commands map + if (Object.getPrototypeOf(command.constructor) === BaseCommand) { + commandsMap.set(command.data.name, command); + } + + // if the command has subcommands, add them to the parent command's subcommands map + else { + const subcommandFile = join(commandDir, '.', 'index.js'); + if (!statSync(subcommandFile).isFile()) return; + + access(subcommandFile, constants.F_OK, async (err) => { + if (err || file === 'index.js') return; + + const parentCommand = Object.getPrototypeOf(command.constructor); + parentCommand.subcommands.set(file.replace('.js', ''), command); + }); + } } } } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 097a9f59..8bb4a5c2 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -4,8 +4,8 @@ import { normal, badge, mascot } from './JSON/emojis.json'; import { createRequire } from 'module'; import 'dotenv/config'; -const fakeRequire = createRequire(import.meta.url); -const emotes = fakeRequire('./JSON/emojis.json'); +const require = createRequire(import.meta.url); +const emotes = require('./JSON/emojis.json'); export const isDevBuild = process.env.NODE_ENV === 'development'; diff --git a/src/utils/Profanity.ts b/src/utils/Profanity.ts index 33fffa8e..d0ec5577 100644 --- a/src/utils/Profanity.ts +++ b/src/utils/Profanity.ts @@ -4,8 +4,8 @@ import { createRequire } from 'node:module'; import badwordsType from './JSON/profanity.json'; // create a require a ESM doesn't support importing JSON -const fakeRequire = createRequire(import.meta.url); -const badwords = fakeRequire('./JSON/profanity.json') as typeof badwordsType; +const require = createRequire(import.meta.url); +const badwords = require('./JSON/profanity.json') as typeof badwordsType; /** * Checks if a message contains any bad words. diff --git a/tsconfig.json b/tsconfig.json index 6bfba989..553dd1ca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,6 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true }, - "include": ["src/**/*.ts", "src/**/*.json", "src/utils/Translator.cts",], + "include": ["src/**/*.ts", "src/**/*.json", "src/**/*.cts",], "exclude": ["node_modules"], } From 36f12a064408a1220f7342d485b583d33f36e37c Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sun, 22 Oct 2023 15:37:45 +0530 Subject: [PATCH 05/13] some bug fixes, add cooldowns, add unit test --- .gitignore | 3 +- jest.config.ts | 17 ++ src/InterChat.ts | 15 +- src/SuperClient.ts | 13 +- src/commands/BaseCommand.ts | 1 + src/commands/context-menu/blacklist.ts | 33 ++- src/commands/context-menu/editMsg.ts | 6 +- src/commands/context-menu/messageInfo.ts | 6 +- src/commands/context-menu/translate.ts | 6 +- src/commands/slash/Information/help.ts | 14 +- src/commands/slash/Information/invite.ts | 9 +- src/commands/slash/Information/stats.ts | 13 +- src/commands/slash/Main/blacklist/server.ts | 44 ++-- src/commands/slash/Main/blacklist/user.ts | 15 +- src/commands/slash/Main/connection.ts | 112 ++++++--- src/commands/slash/Main/hub/browse.ts | 128 +++++----- src/commands/slash/Main/hub/connections.ts | 2 +- src/commands/slash/Main/hub/create.ts | 11 +- src/commands/slash/Main/hub/delete.ts | 5 +- src/commands/slash/Main/hub/index.ts | 152 +++++------ src/commands/slash/Main/hub/invite.ts | 5 +- src/commands/slash/Main/hub/join.ts | 12 +- src/commands/slash/Main/hub/joined.ts | 62 +++++ src/commands/slash/Main/hub/leave.ts | 4 +- src/commands/slash/Main/hub/manage.ts | 42 ++-- src/commands/slash/Main/hub/moderator.ts | 6 +- src/commands/slash/Main/hub/settings.ts | 19 +- src/commands/slash/Staff/purge.ts | 5 +- src/commands/slash/Support/support/report.ts | 10 +- src/commands/slash/Support/support/server.ts | 4 +- src/decorators/Interaction.ts | 2 +- src/index.ts | 19 +- src/managers/BlacklistManager.ts | 238 ++++++++++++++++++ .../CommandManager.ts | 67 ++++- .../NetworkManager.ts | 40 ++- src/scripts/network/buildEmbed.ts | 27 +- src/scripts/network/onboarding.ts | 16 +- src/services/CooldownService.ts | 42 ++++ .../SchedulerService.ts} | 26 ++ src/structures/BlacklistManager.ts | 199 --------------- src/structures/CustomID.ts | 11 +- src/structures/NSFWDetection.ts | 21 +- src/typings/index.d.ts | 9 +- src/updater/ReactionUpdater.ts | 64 +++-- src/utils/Constants.ts | 5 +- src/utils/RegisterCommands.ts | 4 +- src/utils/Utils.ts | 28 ++- tests/HubCreate.test.ts | 108 ++++++++ tests/ImgurLinks.test.ts | 39 +++ 49 files changed, 1104 insertions(+), 635 deletions(-) create mode 100644 jest.config.ts create mode 100644 src/commands/slash/Main/hub/joined.ts create mode 100644 src/managers/BlacklistManager.ts rename src/{structures => managers}/CommandManager.ts (59%) rename src/{structures => managers}/NetworkManager.ts (93%) create mode 100644 src/services/CooldownService.ts rename src/{structures/Scheduler.ts => services/SchedulerService.ts} (66%) delete mode 100644 src/structures/BlacklistManager.ts create mode 100644 tests/HubCreate.test.ts create mode 100644 tests/ImgurLinks.test.ts diff --git a/.gitignore b/.gitignore index 14a14b83..342275ba 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,7 @@ tsconfig.tsbuildinfo # Configuration .env -# Unit tests -tests/ +# Unit test coverage __tests__/ # Unit test coverage reports diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..ca8d58e8 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const jestConfig: JestConfigWithTsJest = { + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.ts?$': [ + 'ts-jest', + { useESM: true }, + ], + }, +}; + +export default jestConfig; \ No newline at end of file diff --git a/src/InterChat.ts b/src/InterChat.ts index f9c696f9..72d7d856 100644 --- a/src/InterChat.ts +++ b/src/InterChat.ts @@ -1,7 +1,7 @@ import db from './utils/Db.js'; import SuperClient from './SuperClient.js'; -import CommandManager from './structures/CommandManager.js'; -import { NetworkMessage } from './structures/NetworkManager.js'; +import CommandManager from './managers/CommandManager.js'; +import { NetworkMessage } from './managers/NetworkManager.js'; class InterChat extends SuperClient { public constructor() { @@ -26,18 +26,21 @@ class InterChat extends SuperClient { }); // handle slash/ctx commands - this.on('interactionCreate', (interaction) => this.getCommandManager().handleInteraction(interaction)); + this.on('interactionCreate', (interaction) => + this.getCommandManager().handleInteraction(interaction), + ); // handle network reactions - this.on('messageReactionAdd', (reaction, user) => this.getReactionUpdater().listenForReactions(reaction, user)); + this.on('messageReactionAdd', (reaction, user) => + this.getReactionUpdater().listenForReactions(reaction, user), + ); - // handle network messages + // handle messages this.on('messageCreate', async (message) => { if (message.author.bot || message.system || message.webhookId) return; this.getNetworkManager().handleNetworkMessage(message as NetworkMessage); }); - } } diff --git a/src/SuperClient.ts b/src/SuperClient.ts index f48cd0f8..f58e3b57 100644 --- a/src/SuperClient.ts +++ b/src/SuperClient.ts @@ -10,15 +10,16 @@ import { import { ClusterClient, getInfo } from 'discord-hybrid-sharding'; import { commandsMap, interactionsMap } from './commands/BaseCommand.js'; import Logger from './utils/Logger.js'; -import Scheduler from './structures/Scheduler.js'; +import Sentry from '@sentry/node'; +import Scheduler from './services/SchedulerService.js'; import NSFWClient from './structures/NSFWDetection.js'; -import CommandManager from './structures/CommandManager.js'; -import NetworkManager from './structures/NetworkManager.js'; +import CommandManager from './managers/CommandManager.js'; +import NetworkManager from './managers/NetworkManager.js'; import ReactionUpdater from './updater/ReactionUpdater.js'; -import BlacklistManager from './structures/BlacklistManager.js'; +import BlacklistManager from './managers/BlacklistManager.js'; import { RemoveMethods } from './typings/index.js'; -import Sentry from '@sentry/node'; import { isDevBuild } from './utils/Constants.js'; +import CooldownService from './services/CooldownService.js'; export default abstract class SuperClient extends Client { readonly logger = Logger; @@ -28,7 +29,7 @@ export default abstract class SuperClient extends Client { readonly commands = commandsMap; readonly interactions = interactionsMap; - readonly commandCooldowns = new Collection(); + readonly commandCooldowns = new CooldownService(); readonly reactionCooldowns = new Collection(); readonly cluster = new ClusterClient(this); diff --git a/src/commands/BaseCommand.ts b/src/commands/BaseCommand.ts index 340d10f0..e4891e12 100644 --- a/src/commands/BaseCommand.ts +++ b/src/commands/BaseCommand.ts @@ -17,6 +17,7 @@ export const interactionsMap = new Collection; diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts index b0ed94e1..6bc948b8 100644 --- a/src/commands/context-menu/blacklist.ts +++ b/src/commands/context-menu/blacklist.ts @@ -3,7 +3,6 @@ import { ApplicationCommandType, ButtonBuilder, ButtonStyle, - CacheType, EmbedBuilder, MessageComponentInteraction, MessageContextMenuCommandInteraction, @@ -17,8 +16,9 @@ import BaseCommand from '../BaseCommand.js'; import db from '../../utils/Db.js'; import { emojis } from '../../utils/Constants.js'; import { CustomID } from '../../structures/CustomID.js'; -import { Interaction } from '../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; import { errorEmbed } from '../../utils/Utils.js'; +import parse from 'parse-duration'; export default class Blacklist extends BaseCommand { data: RESTPostAPIApplicationCommandsJSONBody = { @@ -44,7 +44,7 @@ export default class Blacklist extends BaseCommand { interaction.reply({ embeds: [ errorEmbed( - 'This message was not sent in a network, has expired or you lack required permissions to perform this action.', + `${emojis.info} This message was not sent in a hub, has expired, or you lack permissions to perform this action.`, ), ], ephemeral: true, @@ -89,13 +89,13 @@ export default class Blacklist extends BaseCommand { await interaction.reply({ embeds: [embed], components: [buttons] }); } - @Interaction('blacklist') - async handleComponents(interaction: MessageComponentInteraction): Promise { + @RegisterInteractionHandler('blacklist') + async handleComponents(interaction: MessageComponentInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); if (interaction.user.id !== customId.args[0]) { await interaction.reply({ - embeds: [errorEmbed('This is not your action to perform. Use the command yourself.')], + embeds: [errorEmbed('Sorry, you can\'t perform this action. Please use the command yourself.')], ephemeral: true, }); return; @@ -136,8 +136,8 @@ export default class Blacklist extends BaseCommand { await interaction.showModal(modal); } - @Interaction('blacklist_modal') - async handleModals(interaction: ModalSubmitInteraction): Promise { + @RegisterInteractionHandler('blacklist_modal') + async handleModals(interaction: ModalSubmitInteraction): Promise { await interaction.deferUpdate(); const customId = CustomID.parseCustomId(interaction.customId); @@ -157,8 +157,8 @@ export default class Blacklist extends BaseCommand { } const reason = interaction.fields.getTextInputValue('reason'); - const duration = parseInt(interaction.fields.getTextInputValue('duration')); - const expires = !isNaN(duration) ? new Date(Date.now() + duration) : undefined; + const duration = parse(interaction.fields.getTextInputValue('duration')); + const expires = duration ? new Date(Date.now() + duration) : undefined; const successEmbed = new EmbedBuilder().setColor('Green').addFields( { @@ -193,7 +193,7 @@ export default class Blacklist extends BaseCommand { } if (user) { blacklistManager - .notifyBlacklist(user, messageInDb.hubId, expires, reason) + .notifyBlacklist('user', messageInDb.authorId, messageInDb.hubId, expires, reason) .catch(() => null); } @@ -212,6 +212,16 @@ export default class Blacklist extends BaseCommand { reason, expires, ); + + // Notify server of blacklist + await blacklistManager.notifyBlacklist( + 'server', + messageInDb.serverId, + messageInDb.hubId, + expires, + reason, + ); + await db.connectedList.deleteMany({ where: { serverId: messageInDb.serverId, hubId: messageInDb.hubId }, }); @@ -225,7 +235,6 @@ export default class Blacklist extends BaseCommand { ); } - // TODO: Notify server of blacklist await interaction.editReply({ embeds: [successEmbed], components: [] }); } } diff --git a/src/commands/context-menu/editMsg.ts b/src/commands/context-menu/editMsg.ts index 6daa7a0a..c7846f39 100644 --- a/src/commands/context-menu/editMsg.ts +++ b/src/commands/context-menu/editMsg.ts @@ -16,7 +16,7 @@ import { HubSettingsBitField } from '../../utils/BitFields.js'; import { emojis } from '../../utils/Constants.js'; import { checkIfStaff, hasVoted, replaceLinks } from '../../utils/Utils.js'; import { censor } from '../../utils/Profanity.js'; -import { Interaction } from '../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; import { CustomID } from '../../structures/CustomID.js'; export default class DeleteMessage extends BaseCommand { @@ -77,7 +77,7 @@ export default class DeleteMessage extends BaseCommand { // if (interaction.inCachedGuild()) networkMsgUpdate(interaction.member, target, newMessage); } - @Interaction('editMsg') + @RegisterInteractionHandler('editMsg') async handleModals(interaction: ModalSubmitInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const messageId = customId.args[0]; @@ -106,7 +106,7 @@ export default class DeleteMessage extends BaseCommand { newMessage.includes('dsc.gg') ) { await interaction.editReply( - `${emojis.no} Do not advertise or promote servers in the network. Set an invite in \`/network manage\` instead!`, + `${emojis.no} Do not advertise or promote servers in the network. Set an invite in \`/connection\` instead!`, ); return; } diff --git a/src/commands/context-menu/messageInfo.ts b/src/commands/context-menu/messageInfo.ts index c3700648..64f1d080 100644 --- a/src/commands/context-menu/messageInfo.ts +++ b/src/commands/context-menu/messageInfo.ts @@ -17,7 +17,7 @@ import { profileImage } from 'discord-arts'; import { colors, emojis } from '../../utils/Constants.js'; import BaseCommand from '../BaseCommand.js'; import { CustomID } from '../../structures/CustomID.js'; -import { Interaction } from '../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; export default class MessageInfo extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { @@ -89,7 +89,7 @@ export default class MessageInfo extends BaseCommand { }); } - @Interaction('msgInfo') + @RegisterInteractionHandler('msgInfo') async handleComponents(interaction: MessageComponentInteraction) { // create a variable to store the profile card buffer const customId = CustomID.parseCustomId(interaction.customId); @@ -155,7 +155,7 @@ export default class MessageInfo extends BaseCommand { ? `https://cdn.discordapp.com/icons/${server.id}/${server.banner}.png` : null; const inviteString = guildConnected?.invite - ? `[\`${guildConnected.invite}\`](https://discord.gg/${guildConnected.invite})` + ? `${guildConnected.invite}` : 'Not Set.'; const serverEmbed = new EmbedBuilder() diff --git a/src/commands/context-menu/translate.ts b/src/commands/context-menu/translate.ts index e50e5f17..fce6fd9c 100644 --- a/src/commands/context-menu/translate.ts +++ b/src/commands/context-menu/translate.ts @@ -17,7 +17,7 @@ import db from '../../utils/Db.js'; import BaseCommand from '../BaseCommand.js'; import { emojis } from '../../utils/Constants.js'; import { hasVoted } from '../../utils/Utils.js'; -import { Interaction } from '../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; import { CustomID } from '../../structures/CustomID.js'; import { supportedLanguages } from '@translate-tools/core/translators/GoogleTranslator/index.js'; import translator from '../../utils/Translator.cjs'; @@ -89,7 +89,7 @@ export default class Translate extends BaseCommand { }); } - @Interaction('translate') + @RegisterInteractionHandler('translate') async handleComponents(interaction: ButtonInteraction) { const modal = new ModalBuilder() .setCustomId('translate_modal') @@ -116,7 +116,7 @@ export default class Translate extends BaseCommand { await interaction.showModal(modal); } - @Interaction('translate_modal') + @RegisterInteractionHandler('translate_modal') async handleModals(interaction: ModalSubmitInteraction) { const originalMessage = interaction.message; if (!originalMessage) return; diff --git a/src/commands/slash/Information/help.ts b/src/commands/slash/Information/help.ts index 5b653290..5cb7cb54 100644 --- a/src/commands/slash/Information/help.ts +++ b/src/commands/slash/Information/help.ts @@ -9,11 +9,11 @@ import { ChatInputCommandInteraction, StringSelectMenuInteraction, } from 'discord.js'; -import { colors, emojis } from '../../../utils/Constants.js'; +import { URLs, colors, emojis } from '../../../utils/Constants.js'; import BaseCommand from '../../BaseCommand.js'; import { getCredits, setComponentExpiry } from '../../../utils/Utils.js'; import { CustomID } from '../../../structures/CustomID.js'; -import { Interaction } from '../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../decorators/Interaction.js'; export default class Help extends BaseCommand { readonly data = { @@ -95,7 +95,7 @@ export default class Help extends BaseCommand { new ButtonBuilder() .setStyle(ButtonStyle.Link) .setLabel('Support') - .setURL('https://discord.gg/6bhXQynAPs'), + .setURL(URLs.SUPPORT_INVITE), new ButtonBuilder() .setStyle(ButtonStyle.Link) .setLabel('Vote me!') @@ -115,7 +115,7 @@ export default class Help extends BaseCommand { ); } - @Interaction('credits') + @RegisterInteractionHandler('credits') async handleComponents(interaction: StringSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); if (interaction.user.id !== customId.args[0]) { @@ -209,7 +209,7 @@ export default class Help extends BaseCommand { const creditsEmbed = EmbedBuilder.from(templateEmbed).setDescription(` ## ${emojis.wand} The Team - InterChat is a project driven by a passionate team dedicated to enhancing the Discord experience. We welcome new members to join our team; if you're interested, please join our support server. + InterChat is a project driven by a passionate team dedicated to enhancing the Discord experience. We welcome new members to join our team; if you're interested, please join our [support server](${URLs.SUPPORT_INVITE}). ${creditsDivider} ${emojis.interchatCircle} **Design:** @@ -223,13 +223,13 @@ export default class Help extends BaseCommand { ${emojis.dotBlue} @${members[2]?.username} ${emojis.dotBlue} @${members[0].username} - ${emojis.staff} **Staff: (Recruiting!)** + ${emojis.staff} **Staff: ([Recruiting!](https://forms.gle/8zu7cxx4XPbEmMXJ9))** ${emojis.dotBlue} @${members[4]?.username} ${emojis.dotBlue} @${members[3]?.username} ${emojis.dotBlue} @${members[5]?.username} ${linksDivider} - [Guide](https://discord-interchat.github.io/docs) • [Invite](https://discord.com/application-directory/769921109209907241) • [Support Server](https://discord.gg/6bhXQynAPs) • [Vote](https://top.gg/bot/769921109209907241/vote) • [Privacy](https://discord-interchat.github.io/legal/privacy) • [Terms](https://discord-interchat.github.io/legal/terms) + [Guide](https://discord-interchat.github.io/docs) • [Invite](https://discord.com/application-directory/769921109209907241) • [Support Server](${URLs.SUPPORT_INVITE}) • [Vote](https://top.gg/bot/769921109209907241/vote) • [Privacy](https://discord-interchat.github.io/docs/legal/privacy) • [Terms](https://discord-interchat.github.io/docs/legal/terms) `); await interaction.editReply({ embeds: [creditsEmbed] }); diff --git a/src/commands/slash/Information/invite.ts b/src/commands/slash/Information/invite.ts index 23541848..e1cdbbeb 100644 --- a/src/commands/slash/Information/invite.ts +++ b/src/commands/slash/Information/invite.ts @@ -6,7 +6,7 @@ import { OAuth2Scopes, } from 'discord.js'; import BaseCommand from '../../BaseCommand.js'; -import { emojis } from '../../../utils/Constants.js'; +import { URLs, emojis } from '../../../utils/Constants.js'; import { stripIndents } from 'common-tags'; export default class Invite extends BaseCommand { @@ -21,7 +21,7 @@ export default class Invite extends BaseCommand { permissions: 292662144192n, }); - const InviteButtons = new ActionRowBuilder().addComponents([ + const InviteButton = new ActionRowBuilder().addComponents([ new ButtonBuilder() .setLabel('Invite Me!') .setURL(inviteLink) @@ -32,10 +32,9 @@ export default class Invite extends BaseCommand { await interaction.reply({ content: stripIndents` Thank you for choosing to invite InterChat. Simply click the button below to invite me! - ! - - **__Support Server__:** https://discord.gg/6bhXQynAPs`, - components: [InviteButtons], + - **__Support Server__:** ${URLs.SUPPORT_INVITE}`, + components: [InviteButton], }); } } diff --git a/src/commands/slash/Information/stats.ts b/src/commands/slash/Information/stats.ts index c57bd4c9..5dd87b8c 100644 --- a/src/commands/slash/Information/stats.ts +++ b/src/commands/slash/Information/stats.ts @@ -9,7 +9,7 @@ import { import db from '../../../utils/Db.js'; import BaseCommand from '../../BaseCommand.js'; import { cpus, totalmem } from 'os'; -import { colors, isDevBuild } from '../../../utils/Constants.js'; +import { URLs, colors, isDevBuild } from '../../../utils/Constants.js'; import { msToReadable } from '../../../utils/Utils.js'; import { stripIndents } from 'common-tags'; @@ -34,7 +34,6 @@ export default class Stats extends BaseCommand { const uptime = msToReadable(interaction.client.uptime); const docsLink = 'https://discord-interchat.github.io/docs'; - const supportServer = 'https://discord.gg/6bhXQynAPs'; const embed = new EmbedBuilder() .setColor(colors.invisible) @@ -86,8 +85,16 @@ export default class Stats extends BaseCommand { ]); const linksRow = new ActionRowBuilder().addComponents( - new ButtonBuilder().setLabel('Support').setStyle(ButtonStyle.Link).setURL(supportServer), new ButtonBuilder().setLabel('Guide').setStyle(ButtonStyle.Link).setURL(docsLink), + new ButtonBuilder() + .setLabel('Support Server') + .setStyle(ButtonStyle.Link) + .setURL(URLs.SUPPORT_INVITE), + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel('Vote!') + .setEmoji('🗳️') + .setURL('https://top.gg/bot/769921109209907241/vote'), new ButtonBuilder() .setLabel('Invite') .setStyle(ButtonStyle.Link) diff --git a/src/commands/slash/Main/blacklist/server.ts b/src/commands/slash/Main/blacklist/server.ts index 3cf0bf17..54d067ec 100644 --- a/src/commands/slash/Main/blacklist/server.ts +++ b/src/commands/slash/Main/blacklist/server.ts @@ -3,8 +3,7 @@ import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; import { emojis } from '../../../../utils/Constants.js'; import db from '../../../../utils/Db.js'; import BlacklistCommand from './index.js'; -import Logger from '../../../../utils/Logger.js'; -import BlacklistManager from '../../../../structures/BlacklistManager.js'; +import BlacklistManager from '../../../../managers/BlacklistManager.js'; import parse from 'parse-duration'; export default class UserBlacklist extends BlacklistCommand { @@ -41,7 +40,9 @@ export default class UserBlacklist extends BlacklistCommand { const expires = duration ? new Date(Date.now() + duration) : undefined; const serverInBlacklist = await BlacklistManager.fetchServerBlacklist(hubInDb.id, serverOpt); - if (serverInBlacklist) {return await interaction.followUp('The server is already blacklisted.');} + if (serverInBlacklist) { + return await interaction.followUp('The server is already blacklisted.'); + } const server = await interaction.client.guilds.fetch(serverOpt).catch(() => null); if (!server) return await interaction.followUp('You have inputted an invalid server ID.'); @@ -50,7 +51,7 @@ export default class UserBlacklist extends BlacklistCommand { await blacklistManager.addServerBlacklist(server.id, hubInDb.id, reason, expires); } catch (err) { - Logger.error(err); + interaction.client.logger.error(err); captureException(err); interaction.followUp( `Failed to blacklist **${server.name}**. Enquire with the bot developer for more information.`, @@ -58,7 +59,9 @@ export default class UserBlacklist extends BlacklistCommand { return; } - if (expires && interaction.guildId) {blacklistManager.scheduleRemoval('server', interaction.guildId, hubInDb.id, expires);} + if (expires && interaction.guildId) { + blacklistManager.scheduleRemoval('server', interaction.guildId, hubInDb.id, expires); + } const successEmbed = new EmbedBuilder() .setDescription(`${emojis.tick} **${server.name}** has been successfully blacklisted!`) @@ -78,36 +81,19 @@ export default class UserBlacklist extends BlacklistCommand { await interaction.followUp({ embeds: [successEmbed] }); - const connected = await db.connectedList.findFirst({ - where: { serverId: serverOpt, hubId: hubInDb.id }, - }); - if (connected) { - // notify the server that they have been blacklisted - const channel = await interaction.client.channels - .fetch(connected.channelId) - .catch(() => null); - if (channel?.isTextBased()) {blacklistManager.notifyBlacklist(channel, hubInDb.id, expires, reason).catch(() => null);} + // notify the server that they have been blacklisted + blacklistManager.notifyBlacklist('server', serverOpt, hubInDb.id, expires, reason); - // delete the connected channel from db so they can't reconnect - await db.connectedList.delete({ where: { channelId: connected.channelId } }); - } + // delete all connections from db so they can't reconnect to the hub + await db.connectedList.deleteMany({ where: { serverId: server.id, hubId: hubInDb.id } }); } else if (subCommandGroup == 'remove') { - const blacklistedServer = await db.blacklistedServers.findFirst({ - where: { serverId: serverOpt, hubs: { some: { hubId: hubInDb.id } } }, - }); - if (!blacklistedServer) { - return await interaction.followUp({ - content: 'The server is not blacklisted.', - ephemeral: true, - }); - } - - await blacklistManager.removeBlacklist('server', hubInDb.id, blacklistedServer.serverId); + const result = await blacklistManager.removeBlacklist('server', hubInDb.id, serverOpt); + if (!result) return await interaction.followUp('The server is not blacklisted.'); // Using name from DB since the bot can't access server through API. await interaction.followUp( - `The server **${blacklistedServer.serverName}** has been removed from the blacklist.`, + `The server **${result.serverName}** has been removed from the blacklist.`, ); } } diff --git a/src/commands/slash/Main/blacklist/user.ts b/src/commands/slash/Main/blacklist/user.ts index c7328be6..f3eb18a4 100644 --- a/src/commands/slash/Main/blacklist/user.ts +++ b/src/commands/slash/Main/blacklist/user.ts @@ -1,7 +1,7 @@ import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; import db from '../../../../utils/Db.js'; import BlacklistCommand from './index.js'; -import BlacklistManager from '../../../../structures/BlacklistManager.js'; +import BlacklistManager from '../../../../managers/BlacklistManager.js'; import parse from 'parse-duration'; import { emojis } from '../../../../utils/Constants.js'; @@ -42,7 +42,7 @@ export default class Server extends BlacklistCommand { await interaction.client.users.fetch(userOpt).catch(() => null); if (!user) return interaction.followUp('Could not find user. Use an ID instead.'); - if (user.id === interaction.user.id) return interaction.followUp('You cannot blacklist yourself.'); + // if (user.id === interaction.user.id) return interaction.followUp('You cannot blacklist yourself.'); if (user.id === interaction.client.user?.id) return interaction.followUp('You cannot blacklist the bot wtf.'); const userInBlacklist = await BlacklistManager.fetchUserBlacklist(hubInDb.id, userOpt); @@ -54,7 +54,7 @@ export default class Server extends BlacklistCommand { const expires = duration ? new Date(Date.now() + duration) : undefined; await blacklistManager.addUserBlacklist(hubInDb.id, user.id, String(reason), expires); if (expires) blacklistManager.scheduleRemoval('user', user.id, hubInDb.id, expires); - blacklistManager.notifyBlacklist(user, hubInDb.id, expires, String(reason)); + blacklistManager.notifyBlacklist('user', user.id, hubInDb.id, expires, String(reason)); const successEmbed = new EmbedBuilder() .setDescription(`${emojis.tick} **${user.username}** has been successfully blacklisted!`) @@ -77,13 +77,12 @@ export default class Server extends BlacklistCommand { else if (subcommandGroup == 'remove') { - const blacklistedUser = await BlacklistManager.fetchUserBlacklist(hubInDb.id, userId); - const user = await interaction.client.users.fetch(userId).catch(() => null); + const result = await blacklistManager.removeBlacklist('user', hubInDb.id, userId); + if (!result) return interaction.followUp('The inputted user is not blacklisted.'); - if (!blacklistedUser) return interaction.followUp('The inputted user is not blacklisted.'); - await blacklistManager.removeBlacklist('user', hubInDb.id, blacklistedUser.userId); - await interaction.followUp(`**${user?.username || blacklistedUser?.username}** has been removed from the blacklist.`); + const user = await interaction.client.users.fetch(userId).catch(() => null); + await interaction.followUp(`**${user?.username}** has been removed from the blacklist.`); // TODO: Logging // if (user) { diff --git a/src/commands/slash/Main/connection.ts b/src/commands/slash/Main/connection.ts index 2e79ad00..00cd6de2 100644 --- a/src/commands/slash/Main/connection.ts +++ b/src/commands/slash/Main/connection.ts @@ -19,12 +19,17 @@ import { } from 'discord.js'; import BaseCommand from '../../BaseCommand.js'; import db from '../../../utils/Db.js'; -import { Interaction } from '../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../decorators/Interaction.js'; import { buildEmbed } from '../../../scripts/network/buildEmbed.js'; import { buildConnectionButtons } from '../../../scripts/network/components.js'; import { emojis } from '../../../utils/Constants.js'; import { CustomID } from '../../../structures/CustomID.js'; -import { disableComponents, errorEmbed, getOrCreateWebhook } from '../../../utils/Utils.js'; +import { + disableComponents, + errorEmbed, + getOrCreateWebhook, + setComponentExpiry, +} from '../../../utils/Utils.js'; export default class Connection extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { @@ -48,7 +53,7 @@ export default class Connection extends BaseCommand { const isInDb = await networkManager.fetchConnection({ channelId }); if (!isInDb) { - interaction.reply({ + await interaction.reply({ content: `${emojis.no} This connection does not exist.`, ephemeral: true, }); @@ -118,6 +123,11 @@ export default class Connection extends BaseCommand { }); // TODO Button expiration + setComponentExpiry( + interaction.client.getScheduler(), + await interaction.fetchReply(), + 60 * 10_000, + ); } async autocomplete(interaction: AutocompleteInteraction): Promise { @@ -143,18 +153,14 @@ export default class Connection extends BaseCommand { interaction.respond(await Promise.all(filtered)); } - @Interaction('connection') + @RegisterInteractionHandler('connection') async handleComponents(interaction: MessageComponentInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const channelId = customId.args[0]; if (customId.args.at(1) && customId.args[1] !== interaction.user.id) { interaction.reply({ - embeds: [ - errorEmbed( - `${emojis.no} This button is not for you. Execute the command yourself to use this button.`, - ), - ], + embeds: [errorEmbed('Sorry, you can\'t perform this action. Please use the command yourself.')], ephemeral: true, }); return; @@ -253,7 +259,27 @@ export default class Connection extends BaseCommand { } case 'embed_color': { - // TODO + const modal = new ModalBuilder() + .setTitle('Set Embed Color') + .setCustomId( + new CustomID() + .setIdentifier('connectionModal', 'embed_color') + .addArgs(channelId) + .toString(), + ) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('embed_color') + .setStyle(TextInputStyle.Short) + .setLabel('Embed Color') + .setPlaceholder('Provide a hex color code or leave blank to remove.') + .setValue(isInDb.embedColor || '#000000') + .setRequired(false), + ), + ); + + await interaction.showModal(modal); break; } @@ -294,33 +320,61 @@ export default class Connection extends BaseCommand { } } - @Interaction('connectionModal') + @RegisterInteractionHandler('connectionModal') async handleModals(interaction: ModalSubmitInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); - if (customId.prefix !== 'connectionModal') return; + if (customId.postfix === 'invite') { + const invite = interaction.fields.getTextInputValue('connInviteField'); + const channelId = customId.args[0]; + const networkManager = interaction.client.getNetworkManager(); + + if (!invite) { + await networkManager.updateConnection({ channelId }, { invite: { unset: true } }); + await interaction.reply({ content: `${emojis.yes} Invite Removed.`, ephemeral: true }); + return; + } - const invite = interaction.fields.getTextInputValue('connInviteField'); - const channelId = customId.args[0]; - const networkManager = interaction.client.getNetworkManager(); + const isValid = await interaction.client?.fetchInvite(invite).catch(() => null); - if (!invite) { - await networkManager.updateConnection({ channelId }, { invite: { unset: true } }); - await interaction.reply({ content: `${emojis.yes} Invite Removed.`, ephemeral: true }); - return; - } + if (isValid?.guild?.id !== interaction.guildId) { + await interaction.reply({ content: `${emojis.no} Invalid Invite.`, ephemeral: true }); + return; + } - const isValid = await interaction.client?.fetchInvite(invite).catch(() => null); + await networkManager.updateConnection({ channelId }, { invite }); - if (isValid?.guild?.id !== interaction.guildId) { - await interaction.reply({ content: `${emojis.no} Invalid Invite.`, ephemeral: true }); - return; + await interaction.reply({ + content: `${emojis.yes} Invite Added. Others can now join the server by using \`Message Info\` Apps command in the network.`, + ephemeral: true, + }); } + else if (customId.postfix === 'embed_color') { + const embedColor = interaction.fields.getTextInputValue('embed_color'); + + const hex_regex = /^#[0-9A-F]{6}$/i; + if (embedColor && !hex_regex.test(embedColor)) { + interaction.reply({ + content: `${emojis.no} Invalid hex color code. Please try again.`, + ephemeral: true, + }); + return; + } - await networkManager.updateConnection({ channelId }, { invite }); + await db.connectedList.update({ + where: { channelId: customId.args[0] }, + data: { embedColor: embedColor ? embedColor : { unset: true } }, + }); - await interaction.reply({ - content: `${emojis.yes} Invite Added. Others can now join the server by using \`Message Info\` Apps command in the network.`, - ephemeral: true, - }); + await interaction.reply({ + content: `${emojis.yes} Embed color successfully ${ + embedColor ? `set to \`${embedColor}\`!` : 'unset' + }`, + ephemeral: true, + }); + } + + interaction.message + ?.edit({ embeds: [await buildEmbed(interaction, customId.args[0])] }) + .catch(() => null); } } diff --git a/src/commands/slash/Main/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts index a7148c13..6a023113 100644 --- a/src/commands/slash/Main/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -22,7 +22,7 @@ import { paginate } from '../../../../utils/Pagination.js'; import { calculateAverageRating, getOrCreateWebhook } from '../../../../utils/Utils.js'; import { showOnboarding } from '../../../../scripts/network/onboarding.js'; import { CustomID } from '../../../../structures/CustomID.js'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { stripIndents } from 'common-tags'; export default class Browse extends Hub { @@ -33,7 +33,7 @@ export default class Browse extends Hub { | 'popular' | 'recent' | undefined; - const hubName = interaction.options.getString('search') || undefined; + const hubName = interaction.options.getString('hub') ?? undefined; let sortedHubs: hubs[] = []; @@ -128,10 +128,21 @@ export default class Browse extends Hub { }); } - @Interaction('hub_browse') + @RegisterInteractionHandler('hub_browse') async handleComponents(interaction: ButtonInteraction | ChannelSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); + const hubDetails = await db.hubs.findFirst({ + where: { id: customId.args[0] }, + include: { connections: true }, + }); + if (!hubDetails) { + return await interaction.reply({ + content: 'Hub not found.', + ephemeral: true, + }); + } + if (customId.postfix === 'rate') { const ratingModal = new ModalBuilder() .setCustomId( @@ -152,21 +163,7 @@ export default class Browse extends Hub { ); await interaction.showModal(ratingModal); } - - const hubDetails = await db.hubs.findFirst({ - where: { id: customId.args[0] }, - include: { connections: true }, - }); - - - if (customId.postfix === 'join') { - if (!hubDetails) { - return await interaction.reply({ - content: 'Hub not found.', - ephemeral: true, - }); - } - + else if (customId.postfix === 'join') { const alreadyJoined = hubDetails.connections.find((c) => c.serverId === interaction.guildId); if (alreadyJoined) { interaction.reply({ @@ -223,14 +220,10 @@ export default class Browse extends Hub { ephemeral: true, }); } - - - else if (interaction.customId === 'cancel') { + else if (customId.postfix === 'cancel') { await interaction.deleteReply().catch(() => null); return; } - - else if (customId.postfix === 'channel_select' || customId.postfix === 'confirm') { if (!hubDetails) { return await interaction.reply({ @@ -242,12 +235,15 @@ export default class Browse extends Hub { if (!interaction.inCachedGuild()) return; const channel = interaction.isChannelSelectMenu() - ? (interaction.guild?.channels.cache.get(interaction.values[0])) + ? interaction.guild?.channels.cache.get(interaction.values[0]) : interaction.channel; // for type safety if (channel?.type !== ChannelType.GuildText && !channel?.isThread()) { - await interaction.update(`${emojis.no} Only text and thread channels are supported!`); + await interaction.reply({ + content: `${emojis.no} Only text and thread channels are supported!`, + ephemeral: true, + }); return; } @@ -265,51 +261,63 @@ export default class Browse extends Hub { return; } - if (interaction.customId === 'confirm' || interaction.customId === 'channel_select') { - const channelConnected = await db.connectedList.findFirst({ - where: { channelId: channel.id }, - }); + const channelConnected = await db.connectedList.findFirst({ + where: { channelId: channel.id }, + }); - if (channelConnected) { - interaction.update({ - content: 'This channel is already connected to another hub!', - embeds: [], - components: [], - }); - return; - } - - // Show new users rules & info about network, also prevents user from joining twice - const onboardingCompleted = await showOnboarding(interaction, hubDetails.name, channel.id); - // if user cancels onboarding or it times out - if (!onboardingCompleted) return await interaction.deleteReply().catch(() => null); - - const webhook = await getOrCreateWebhook(channel); - if (!webhook) return; - - const networkManager = interaction.client.getNetworkManager(); - // finally make the connection - await networkManager.createConnection({ - serverId: channel.guildId, - channelId: channel.id, - parentId: channel.isThread() ? channel.parentId : undefined, - webhookURL: webhook.url, - hub: { connect: { id: hubDetails.id } }, - connected: true, - compact: false, - profFilter: true, + if (channelConnected) { + await interaction.update({ + content: 'This channel is already connected to another hub!', + embeds: [], + components: [], }); + return; + } - await interaction.editReply({ - content: `Successfully joined hub ${hubDetails.name} from ${channel}! Use \`/network manage\` to manage your connection. And \`/hub leave\` to leave the hub.`, + // Show new users rules & info about network, also prevents user from joining twice + const onboardingCompleted = await showOnboarding( + interaction, + hubDetails.name, + channel.id, + true, + ); + // if user cancels onboarding or it times out + if (!onboardingCompleted) { + return await interaction.deleteReply().catch(() => null); + } + else if (onboardingCompleted === 'in-progress') { + return await interaction.update({ + content: `There has already been an attempting to join a hub from ${channel}. Please cancel it or wait for it to complete.`, embeds: [], components: [], }); } + + const webhook = await getOrCreateWebhook(channel); + if (!webhook) return; + + const networkManager = interaction.client.getNetworkManager(); + // finally make the connection + await networkManager.createConnection({ + serverId: channel.guildId, + channelId: channel.id, + parentId: channel.isThread() ? channel.parentId : undefined, + webhookURL: webhook.url, + hub: { connect: { id: hubDetails.id } }, + connected: true, + compact: false, + profFilter: true, + }); + + await interaction.editReply({ + content: `Successfully joined hub ${hubDetails.name} from ${channel}! Use \`/connection\` to manage your connection. And \`/hub leave\` to leave the hub.`, + embeds: [], + components: [], + }); } } - @Interaction('hub_browse_modal') + @RegisterInteractionHandler('hub_browse_modal') async handleModals(interaction: ModalSubmitInteraction) { const customId = CustomID.parseCustomId(interaction.customId); diff --git a/src/commands/slash/Main/hub/connections.ts b/src/commands/slash/Main/hub/connections.ts index 4024f737..3148a45c 100644 --- a/src/commands/slash/Main/hub/connections.ts +++ b/src/commands/slash/Main/hub/connections.ts @@ -59,7 +59,7 @@ export default class Connections extends Hub { value += '\n' + stripIndent` Joined At: - Invite: ${setup.invite ? `https://discord.gg/${setup.invite}` : 'Not Set.'} + Invite: ${setup.invite ? setup.invite : 'Not Set.'} `; } diff --git a/src/commands/slash/Main/hub/create.ts b/src/commands/slash/Main/hub/create.ts index 3f132f26..677aa222 100644 --- a/src/commands/slash/Main/hub/create.ts +++ b/src/commands/slash/Main/hub/create.ts @@ -11,13 +11,13 @@ import { import Hub from './index.js'; import db from '../../../../utils/Db.js'; import { stripIndents } from 'common-tags'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { HubSettingsBits } from '../../../../utils/BitFields.js'; import { checkAndFetchImgurUrl, errorEmbed } from '../../../../utils/Utils.js'; import { emojis } from '../../../../utils/Constants.js'; export default class Create extends Hub { - // TODO: readonly cooldown = 60 * 60 * 1000; + readonly cooldown = 60 * 60 * 1000; // 1 hour async execute(interaction: ChatInputCommandInteraction) { const modal = new ModalBuilder() @@ -71,7 +71,7 @@ export default class Create extends Hub { await interaction.showModal(modal); } - @Interaction('hub_create_modal') + @RegisterInteractionHandler('hub_create_modal') async handleModals(interaction: ModalSubmitInteraction) { await interaction.deferReply({ ephemeral: true }); @@ -137,8 +137,9 @@ export default class Create extends Hub { }, }); - // FIXME this is a temp cooldown until we have a global cooldown system for commands & subcommands - // cooldowns.set(interaction.user.id, Date.now() + ); + // set cooldown after creating a hub (because a failed hub creation should not trigger the cooldown) + interaction.client.commandCooldowns.setCooldown(`${interaction.user.id}-hub-create`, this.cooldown); // 1 hour + const successEmbed = new EmbedBuilder() .setColor('Green') .setDescription( diff --git a/src/commands/slash/Main/hub/delete.ts b/src/commands/slash/Main/hub/delete.ts index bd9b52da..3159d5dd 100644 --- a/src/commands/slash/Main/hub/delete.ts +++ b/src/commands/slash/Main/hub/delete.ts @@ -13,7 +13,7 @@ import { captureException } from '@sentry/node'; import { emojis } from '../../../../utils/Constants.js'; import { deleteHubs, setComponentExpiry } from '../../../../utils/Utils.js'; import { CustomID } from '../../../../structures/CustomID.js'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; export default class Delete extends Hub { async execute(interaction: ChatInputCommandInteraction) { @@ -64,9 +64,8 @@ export default class Delete extends Hub { setComponentExpiry(interaction.client.getScheduler(), await interaction.fetchReply(), 10_000); } - @Interaction('hub_delete') + @RegisterInteractionHandler('hub_delete') async handleComponents(interaction: ButtonInteraction) { - console.log(interaction.customId); const customId = CustomID.parseCustomId(interaction.customId); const userId = customId.args[0]; const hubId = customId.args[1]; diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts index 5f423986..bb981f48 100644 --- a/src/commands/slash/Main/hub/index.ts +++ b/src/commands/slash/Main/hub/index.ts @@ -199,100 +199,66 @@ export default class Hub extends BaseCommand { }, ], }, + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: 'invite', + description: 'Manage invites for your private hubs.', + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: 'create', + description: '🔗 Create a new invite code to your private hub', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub you wish to create this invite for', + required: true, + autocomplete: true, + }, + { + type: ApplicationCommandOptionType.Number, + name: 'expiry', + description: 'The expiry of the invite link. Eg. 10h (10 hours from now)', + required: false, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'revoke', + description: '🚫 Revoke an invite code to your hub', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'code', + description: 'The invite code', + required: true, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'list', + description: 'List all moderators on a hub', + options: [ + { + type: ApplicationCommandOptionType.String, + name: 'hub', + description: 'The name of the hub', + required: true, + autocomplete: true, + }, + ], + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'joined', + description: '📜 List all hubs you have joined from this server.', + }, ], - /* - .addSubcommandGroup((subcommandGroup) => - subcommandGroup - .setName('moderator') - .setDescription('Manage hub moderators') - .addSubcommand((subcommand) => - subcommand - .setName('add') - .setDescription('Add a new hub moderator') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub you wish to add moderators to') - .setAutocomplete(true) - .setRequired(true), - ) - .addUserOption(stringOpt => - stringOpt - .setName('user') - .setDescription('User who will become hub moderator') - .setRequired(true), - ) - .addStringOption(stringOpt => - stringOpt - .setName('role') - .setDescription('Determines what hub permissions they have') - .addChoices( - { name: 'Network Moderator', value: 'network_mod' }, - { name: 'Hub Manager', value: 'manager' }, - ) - .setRequired(false), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription('Remove a user from moderator position in your hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub you wish to add moderators to') - .setAutocomplete(true) - .setRequired(true), - ) - .addUserOption(userOpt => - userOpt - .setName('user') - .setDescription('The user who should be removed') - .setRequired(true), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('update') - .setDescription('Update the role of a hub moderator') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ) - .addUserOption(userOpt => - userOpt - .setName('user') - .setDescription('The moderator you wish the change') - .setRequired(true), - ) - .addStringOption(stringOpt => - stringOpt - .setName('role') - .setDescription('The moderator role to update') - .setRequired(true) - .addChoices( - { name: 'Network Moderator', value: 'network_mod' }, - { name: 'Hub Manager', value: 'manager' }, - ), - ), - ) - .addSubcommand((subcommand) => - subcommand - .setName('list') - .setDescription('List all moderators on a hub') - .addStringOption(stringOpt => - stringOpt - .setName('hub') - .setDescription('The name of the hub') - .setAutocomplete(true) - .setRequired(true), - ), - ), - ) - */ }; // subcommand classes are added to this map in their respective files diff --git a/src/commands/slash/Main/hub/invite.ts b/src/commands/slash/Main/hub/invite.ts index 6a937945..b8c6e234 100644 --- a/src/commands/slash/Main/hub/invite.ts +++ b/src/commands/slash/Main/hub/invite.ts @@ -4,9 +4,10 @@ import { captureException } from '@sentry/node'; import { stripIndents } from 'common-tags'; import { emojis } from '../../../../utils/Constants.js'; import db from '../../../../utils/Db.js'; -import Logger from '../../../../utils/Logger.js'; export default class Invite extends Hub { + readonly cooldown = 3000; // 3 seconds + async execute(interaction: ChatInputCommandInteraction) { const subcommand = interaction.options.getSubcommand(); @@ -96,7 +97,7 @@ export default class Invite extends Hub { }); } catch (e) { - Logger.error(e); + interaction.client.logger.error(e); captureException(e); await interaction .reply({ diff --git a/src/commands/slash/Main/hub/join.ts b/src/commands/slash/Main/hub/join.ts index ed91ac6c..f0c1a30b 100644 --- a/src/commands/slash/Main/hub/join.ts +++ b/src/commands/slash/Main/hub/join.ts @@ -2,7 +2,7 @@ import { ChannelType, ChatInputCommandInteraction } from 'discord.js'; import { emojis } from '../../../../utils/Constants.js'; import Hub from './index.js'; import db from '../../../../utils/Db.js'; -import BlacklistManager from '../../../../structures/BlacklistManager.js'; +import BlacklistManager from '../../../../managers/BlacklistManager.js'; import { hubs } from '@prisma/client'; import { getOrCreateWebhook } from '../../../../utils/Utils.js'; import { showOnboarding } from '../../../../scripts/network/onboarding.js'; @@ -87,7 +87,15 @@ export default class JoinSubCommand extends Hub { // display onboarding message, also prevents user from joining twice const onboardingCompleted = await showOnboarding(interaction, hub.name, channel.id); // if user cancels onboarding or it times out - if (!onboardingCompleted) return await interaction.deleteReply().catch(() => null); + if (!onboardingCompleted) { + return await interaction.deleteReply().catch(() => null); + } + else if (onboardingCompleted === 'in-progress') { + return await interaction.reply({ + content: `${emojis.no} An attempt to join a hub in <#${channel.id}> is currently in progress. Please wait for it to complete before making another attempt.`, + ephemeral: true, + }); + } const webhook = await getOrCreateWebhook(channel); if (!webhook) return; diff --git a/src/commands/slash/Main/hub/joined.ts b/src/commands/slash/Main/hub/joined.ts new file mode 100644 index 00000000..e16aecdb --- /dev/null +++ b/src/commands/slash/Main/hub/joined.ts @@ -0,0 +1,62 @@ +import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { paginate } from '../../../../utils/Pagination.js'; +import Hub from './index.js'; +import { emojis } from '../../../../utils/Constants.js'; +import db from '../../../../utils/Db.js'; + +export default class Joined extends Hub { + async execute(interaction: ChatInputCommandInteraction) { + const connections = await db.connectedList.findMany({ + where: { serverId: interaction.guild?.id }, + include: { hub: true }, + }); + if (connections.length === 0) { + return await interaction.reply(`${emojis.no} You have not joined any hubs yet!`); + } + + const allFields = connections.map((con) => ({ + name: `${con.hub?.name}`, + value: `<#${con.channelId}>`, + inline: true, + })); + + if (allFields.length > 25) { + const paginateEmbeds: EmbedBuilder[] = []; + let currentEmbed: EmbedBuilder | undefined; + + // Split the fields into multiple embeds + allFields.forEach((field, index) => { + if (index % 25 === 0) { + // Start a new embed + currentEmbed = new EmbedBuilder() + .setTitle('Joined hubs') + .setDescription(`This server is a part of **${connections.length}** hub(s).`) + .setColor('Blue') + .setFooter({ + text: 'Use /hub leave to leave a hub.', + }); + paginateEmbeds.push(currentEmbed); + } + + // Add the field to the current embed + if (currentEmbed) { + currentEmbed.addFields(field); + } + }); + + paginate(interaction, paginateEmbeds); + return; + } + + const embed = new EmbedBuilder() + .setTitle('Joined hubs') + .setDescription(`This server is a part of **${connections.length}** hub(s).`) + .setFields(allFields) + .setColor('Blue') + .setFooter({ + text: 'Use /hub leave to leave a hub.', + }); + + await interaction.reply({ embeds: [embed] }); + } +} diff --git a/src/commands/slash/Main/hub/leave.ts b/src/commands/slash/Main/hub/leave.ts index 91eec668..2cf614f0 100644 --- a/src/commands/slash/Main/hub/leave.ts +++ b/src/commands/slash/Main/hub/leave.ts @@ -8,7 +8,7 @@ import { EmbedBuilder, } from 'discord.js'; import Hub from './index.js'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { CustomID } from '../../../../structures/CustomID.js'; import { emojis } from '../../../../utils/Constants.js'; import db from '../../../../utils/Db.js'; @@ -53,7 +53,7 @@ export default class Leave extends Hub { setComponentExpiry(interaction.client.getScheduler(), await interaction.fetchReply(), 10_000); } - @Interaction('hub_leave') + @RegisterInteractionHandler('hub_leave') async handleComponents(interaction: MessageComponentInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const channelId = customId.args[0]; diff --git a/src/commands/slash/Main/hub/manage.ts b/src/commands/slash/Main/hub/manage.ts index 93507bca..209fbc31 100644 --- a/src/commands/slash/Main/hub/manage.ts +++ b/src/commands/slash/Main/hub/manage.ts @@ -15,9 +15,9 @@ import Hub from './index.js'; import { hubs, connectedList } from '@prisma/client'; import { stripIndents } from 'common-tags'; import { emojis } from '../../../../utils/Constants.js'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { CustomID } from '../../../../structures/CustomID.js'; -import { errorEmbed, setComponentExpiry } from '../../../../utils/Utils.js'; +import { checkAndFetchImgurUrl, errorEmbed, setComponentExpiry } from '../../../../utils/Utils.js'; export default class Manage extends Hub { async execute(interaction: ChatInputCommandInteraction) { @@ -52,7 +52,7 @@ export default class Manage extends Hub { ); } - @Interaction('hub_manage') + @RegisterInteractionHandler('hub_manage') async handleComponents(interaction: StringSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); @@ -135,8 +135,9 @@ export default class Manage extends Hub { new ActionRowBuilder().addComponents( new TextInputBuilder() .setLabel('Enter Banner URL') - .setPlaceholder('Enter a valid imgur image URL.') + .setPlaceholder('Enter a valid imgur URL. Leave blank to remove.') .setStyle(TextInputStyle.Short) + .setRequired(false) .setCustomId('banner'), ), ); @@ -170,7 +171,7 @@ export default class Manage extends Hub { } } - @Interaction('hub_manage_modal') + @RegisterInteractionHandler('hub_manage_modal') async handleModals(interaction: ModalSubmitInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const hubName = customId.args[0]; @@ -216,10 +217,8 @@ export default class Manage extends Hub { const newIcon = interaction.fields.getTextInputValue('icon'); // check if icon is a valid imgur link - const imgurLink = newIcon.match( - /\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g, - ); - if (!imgurLink) { + const iconUrl = await checkAndFetchImgurUrl(newIcon); + if (!iconUrl) { await interaction.reply({ content: 'Invalid icon URL. Please make sure it is a valid imgur image URL.', ephemeral: true, @@ -229,7 +228,7 @@ export default class Manage extends Hub { await db.hubs.update({ where: { name: hubName }, - data: { iconUrl: imgurLink[0] }, + data: { iconUrl }, }); await interaction.reply({ @@ -242,12 +241,20 @@ export default class Manage extends Hub { // change banner modal case 'banner': { const newBanner = interaction.fields.getTextInputValue('banner'); - const isImgurUrl = newBanner.match( - /\bhttps?:\/\/i\.imgur\.com\/[A-Za-z0-9]+\.(?:jpg|jpeg|gif|png|bmp)\b/g, - ); + + if (!newBanner) { + await db.hubs.update({ + where: { name: hubName }, + data: { bannerUrl: { unset: true } }, + }); + + await interaction.reply({ content: 'Successfully removed banner!', ephemeral: true }); + } + + const bannerUrl = await checkAndFetchImgurUrl(newBanner); // if banner is not a valid imgur link - if (!isImgurUrl) { + if (!bannerUrl) { await interaction.reply({ content: 'Invalid banner URL. Please make sure it is a valid imgur image URL.', ephemeral: true, @@ -257,13 +264,10 @@ export default class Manage extends Hub { await db.hubs.update({ where: { name: hubName }, - data: { bannerUrl: isImgurUrl[0] }, + data: { bannerUrl }, }); - await interaction.reply({ - content: 'Successfully updated banner!', - ephemeral: true, - }); + await interaction.reply({ content: 'Successfully updated banner!', ephemeral: true }); break; } diff --git a/src/commands/slash/Main/hub/moderator.ts b/src/commands/slash/Main/hub/moderator.ts index a90f806e..6f49ac83 100644 --- a/src/commands/slash/Main/hub/moderator.ts +++ b/src/commands/slash/Main/hub/moderator.ts @@ -33,7 +33,7 @@ export default class Moderator extends Hub { }); } - const position = interaction.options.getString('role') ?? 'network_mod'; + const position = interaction.options.getString('position') ?? 'network_mod'; await db.hubs.update({ where: { id: hub.id }, data: { moderators: { push: { userId: user.id, position } } }, @@ -79,7 +79,7 @@ export default class Moderator extends Hub { case 'update': { const user = interaction.options.getUser('user', true); - const position = interaction.options.getString('role', true); + const position = interaction.options.getString('position', true); if (!hub.moderators.find((mod) => mod.userId === user.id)) { return interaction.reply({ @@ -130,4 +130,4 @@ export default class Moderator extends Hub { break; } } -} \ No newline at end of file +} diff --git a/src/commands/slash/Main/hub/settings.ts b/src/commands/slash/Main/hub/settings.ts index cfe0db10..bf17cba1 100644 --- a/src/commands/slash/Main/hub/settings.ts +++ b/src/commands/slash/Main/hub/settings.ts @@ -11,7 +11,7 @@ import Hub from './index.js'; import { hubs } from '@prisma/client'; import { HubSettingsBitField, HubSettingsString } from '../../../../utils/BitFields.js'; import { colors, emojis } from '../../../../utils/Constants.js'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { CustomID } from '../../../../structures/CustomID.js'; import { StringSelectMenuInteraction } from 'discord.js'; import { errorEmbed } from '../../../../utils/Utils.js'; @@ -45,11 +45,20 @@ export default class Settings extends Hub { await interaction.reply({ embeds: [embed], components: [selects] }); } - @Interaction('hub_settings') + @RegisterInteractionHandler('hub_settings') async handleComponents(interaction: StringSelectMenuInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const hubName = customId.args[0]; + if (interaction.user.id !== customId.args[1]) { + return interaction.reply({ + embeds: [ + errorEmbed('Sorry, you can\'t perform this action. Please run the command yourself.'), + ], + ephemeral: true, + }); + } + // respond to select menu const selected = interaction.values[0] as HubSettingsString; @@ -57,7 +66,11 @@ export default class Settings extends Hub { // & only allow network channels to be marked as NSFW if (selected === 'BlockNSFW') { return interaction.reply({ - embeds: [errorEmbed(`${emojis.no} This setting cannot be changed yet. Please wait for the next update.`)], + embeds: [ + errorEmbed( + `${emojis.no} This setting cannot be changed yet. Please wait for the next update.`, + ), + ], ephemeral: true, }); } diff --git a/src/commands/slash/Staff/purge.ts b/src/commands/slash/Staff/purge.ts index 2089920e..9730e936 100644 --- a/src/commands/slash/Staff/purge.ts +++ b/src/commands/slash/Staff/purge.ts @@ -13,7 +13,6 @@ import { stripIndents } from 'common-tags'; import { emojis } from '../../../utils/Constants.js'; import { messageData as messageDataCol } from '@prisma/client'; import { msToReadable } from '../../../utils/Utils.js'; -import Logger from '../../../utils/Logger.js'; const limitOpt: APIApplicationCommandBasicOption = { type: ApplicationCommandOptionType.Integer, @@ -174,7 +173,7 @@ export default class Purge extends BaseCommand { if (!messagesInDb || messagesInDb.length < 1) { return await interaction.reply({ - content: 'Unable to locate messages to purge. Maybe they have expired?', + content: 'Messages to purge not found; messages sent over 24 hours ago have been automatically removed.', ephemeral: true, }); } @@ -211,7 +210,7 @@ export default class Purge extends BaseCommand { return interaction.client.resolveEval(evalRes) || []; } catch (e) { - Logger.error(e); + interaction.client.logger.error(e); captureException(e); } diff --git a/src/commands/slash/Support/support/report.ts b/src/commands/slash/Support/support/report.ts index 58a35f3d..eb480eb9 100644 --- a/src/commands/slash/Support/support/report.ts +++ b/src/commands/slash/Support/support/report.ts @@ -14,10 +14,10 @@ import { ThreadChannel, } from 'discord.js'; import { stripIndents } from 'common-tags'; -import { channels, colors, emojis } from '../../../../utils/Constants.js'; +import { URLs, channels, colors, emojis } from '../../../../utils/Constants.js'; import Support from './index.js'; import { CustomID } from '../../../../structures/CustomID.js'; -import { Interaction } from '../../../../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; export default class Report extends Support { static readonly reportModal = new ModalBuilder() @@ -93,7 +93,7 @@ export default class Report extends Support { } } - @Interaction('report') + @RegisterInteractionHandler('report') async handleComponents(interaction: MessageComponentInteraction) { if (interaction.isStringSelectMenu()) { const modal = new ModalBuilder() @@ -130,7 +130,7 @@ export default class Report extends Support { } } - @Interaction('report_modal') + @RegisterInteractionHandler('report_modal') async handleModals(interaction: ModalSubmitInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const affected = customId.args[0]; @@ -274,7 +274,7 @@ export default class Report extends Support { } } await interaction.reply({ - content: 'Report submitted. Join the support server to get updates on your report.', + content: `Report submitted. Join the [**support server**](${URLs.SUPPORT_INVITE}) to get updates on your report.`, ephemeral: true, }); } diff --git a/src/commands/slash/Support/support/server.ts b/src/commands/slash/Support/support/server.ts index 1b93e271..426ed109 100644 --- a/src/commands/slash/Support/support/server.ts +++ b/src/commands/slash/Support/support/server.ts @@ -1,12 +1,12 @@ import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import { colors } from '../../../../utils/Constants.js'; +import { URLs, colors } from '../../../../utils/Constants.js'; import Support from './index.js'; export default class SupportServer extends Support { async execute(interaction: ChatInputCommandInteraction) { const embed = new EmbedBuilder() .setTitle('InterChat Central') - .setDescription('[Click Here]()') + .setDescription(`[Click Here](${URLs.SUPPORT_INVITE}) to join the support server.`) .setColor(colors.interchatBlue) .setTimestamp(); await interaction.reply({ embeds: [embed] }); diff --git a/src/decorators/Interaction.ts b/src/decorators/Interaction.ts index 80107923..50ad4b9f 100644 --- a/src/decorators/Interaction.ts +++ b/src/decorators/Interaction.ts @@ -6,7 +6,7 @@ export type InteractionFunction = ( ) => Promise | void; // Decorator function to call a specified method when an interaction is created (ie. interactionCreate event) -export function Interaction(customId: string): MethodDecorator { +export function RegisterInteractionHandler(customId: string): MethodDecorator { return function( targetClass: Record, propertyKey: string | symbol, diff --git a/src/index.ts b/src/index.ts index 721a1f44..10bd3931 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import db from './utils/Db.js'; import Logger from './utils/Logger.js'; -import Scheduler from './structures/Scheduler.js'; -import BlacklistManager from './structures/BlacklistManager.js'; +import Scheduler from './services/SchedulerService.js'; +import BlacklistManager from './managers/BlacklistManager.js'; import { ClusterManager } from 'discord-hybrid-sharding'; import { updateTopGGStats } from './updater/StatsUpdater.js'; import { isDevBuild } from './utils/Constants.js'; @@ -10,11 +10,10 @@ import { wait } from './utils/Utils.js'; import 'dotenv/config'; const manager = new ClusterManager('build/InterChat.js', { - totalShards: 1, + totalShards: 'auto', mode: 'process', token: process.env.TOKEN, shardsPerClusters: 1, - shardArgs: [`--production=${isDevBuild}`], }); manager.spawn({ timeout: -1 }); @@ -47,17 +46,17 @@ const deleteOldMessages = async () => { .catch(() => null); }; -const loopThruBlacklists = (blacklists: (blacklistedServers | blacklistedUsers)[], scheduler: Scheduler) => { +const processAndManageBlacklists = async (blacklists: (blacklistedServers | blacklistedUsers)[], scheduler: Scheduler) => { if (blacklists.length === 0) return; const blacklistManager = new BlacklistManager(scheduler); for (const blacklist of blacklists) { for (const { hubId, expires } of blacklist.hubs) { if (!expires) continue; - if (expires < new Date()) { if ('serverId' in blacklist) blacklistManager.removeBlacklist('server', hubId, blacklist.serverId); - else blacklistManager.removeBlacklist('user', hubId, blacklist.userId); + else await blacklistManager.removeBlacklist('user', hubId, blacklist.userId); + continue; } blacklistManager.scheduleRemoval( @@ -71,7 +70,7 @@ const loopThruBlacklists = (blacklists: (blacklistedServers | blacklistedUsers)[ }; manager.on('clusterCreate', async (cluster) => { - // last cluster + // if it is the last cluster and code is in production if (cluster.id === manager.totalClusters - 1 && !isDevBuild) { // give time for shards to connect await wait(10_000); @@ -90,7 +89,7 @@ manager.on('clusterCreate', async (cluster) => { // remove expired blacklists or set new timers for them const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; - loopThruBlacklists(await db.blacklistedServers.findMany(query), scheduler); - loopThruBlacklists(await db.blacklistedUsers.findMany(query), scheduler); + processAndManageBlacklists(await db.blacklistedServers.findMany(query), scheduler); + processAndManageBlacklists(await db.blacklistedUsers.findMany(query), scheduler); } }); diff --git a/src/managers/BlacklistManager.ts b/src/managers/BlacklistManager.ts new file mode 100644 index 00000000..77bdf4b4 --- /dev/null +++ b/src/managers/BlacklistManager.ts @@ -0,0 +1,238 @@ +import db from '../utils/Db.js'; +import Scheduler from '../services/SchedulerService.js'; +import SuperClient from '../SuperClient.js'; +import { blacklistedServers, blacklistedUsers } from '@prisma/client'; +import { EmbedBuilder } from 'discord.js'; +import { emojis, colors } from '../utils/Constants.js'; +import { captureException } from '@sentry/node'; +import Logger from '../utils/Logger.js'; + +export default class BlacklistManager { + private scheduler: Scheduler; + + constructor(scheduler: Scheduler) { + this.scheduler = scheduler; + } + + /** + * Remove a user or server from the blacklist. + * @param type The type of blacklist to remove. + * @param hubId The hub ID to remove the blacklist from. + * @param userOrServerId The user or server ID to remove from the blacklist. + * @returns The updated blacklist. + */ + async removeBlacklist( + type: 'server', + hubId: string, + serverId: string, + ): Promise; + async removeBlacklist(type: 'user', hubId: string, userId: string): Promise; + async removeBlacklist(type: 'user' | 'server', hubId: string, userOrServerId: string) { + this.scheduler.stopTask(`blacklist_${type}-${userOrServerId}`); + const data = { + hubs: { deleteMany: { where: { hubId } } }, + }; + + if (type === 'user') { + const where = { userId: userOrServerId, hubs: { some: { hubId } } }; + + const notInBlacklist = await db.blacklistedUsers.findFirst({ where }); + if (!notInBlacklist) return; + + return await db.blacklistedUsers.update({ where, data }); + } + else { + const where = { serverId: userOrServerId, hubs: { some: { hubId } } }; + + const notInBlacklist = await db.blacklistedServers.findFirst({ where }); + if (!notInBlacklist) return; + + return await db.blacklistedServers.update({ where, data }); + } + } + + /** + * Schedule the removal of a user or server from the blacklist. + * @param type The type of blacklist to remove. (user/server) + * @param userOrServerId The user or server ID to remove from the blacklist. + * @param hubId The hub ID to remove the blacklist from. + * @param expires The date or milliseconds to wait before removing the blacklist. + */ + async scheduleRemoval( + type: 'server' | 'user', + userOrServerId: string, + hubId: string, + expires: Date | number, + ): Promise { + let name: string; + let execute; + + if (type === 'server') { + if (this.scheduler.taskNames.includes(`unblacklistServer-${userOrServerId}`)) { + this.scheduler.stopTask(`unblacklistServer-${userOrServerId}`); + } + + name = `unblacklistServer-${userOrServerId}`; + execute = () => this.removeBlacklist('server', hubId, userOrServerId); + } + else { + if (this.scheduler.taskNames.includes(`unblacklistUser-${userOrServerId}`)) { + this.scheduler.stopTask(`unblacklistUser-${userOrServerId}`); + } + + name = `unblacklistUser-${userOrServerId}`; + execute = () => this.removeBlacklist('user', hubId, userOrServerId); + } + + this.scheduler.addTask(name, expires, execute); + } + + /** + * Notify a user or server that they have been blacklisted. + * @param type The type of blacklist to notify. (user/server) + * @param userOrServerId The user or server ID to notify. + * @param hubId The hub ID to notify. + * @param expires The date after which the blacklist expires. + * @param reason The reason for the blacklist. + */ + async notifyBlacklist( + type: 'user' | 'server', + userOrServerId: string, + hubId: string, + expires?: Date, + reason: string = 'No reason provided.', + ): Promise { + const hub = await db.hubs.findUnique({ where: { id: hubId } }); + const expireString = expires ? `` : 'Never'; + const embed = new EmbedBuilder() + .setTitle(emojis.blobFastBan + ' Blacklist Notification') + .setColor(colors.interchatBlue) + .setFields( + { name: 'Reason', value: reason, inline: true }, + { name: 'Expires', value: expireString, inline: true }, + ); + + if (type === 'user') { + embed.setDescription(`You have been blacklisted from talking in hub **${hub?.name}**.`); + const user = await SuperClient.getInstance().users.fetch(userOrServerId); + try { + await user.send({ embeds: [embed] }); + } + catch (e) { + Logger.error(e); + captureException(e); + } + } + else { + embed.setDescription( + `This server has been blacklisted from talking in hub **${hub?.name}**.`, + ); + const serverConnected = await db.connectedList.findFirst({ + where: { serverId: userOrServerId, hubId }, + }); + + if (!serverConnected) return; + + await SuperClient.getInstance().cluster.broadcastEval( + async (client, ctx) => { + const channel = await client.channels.fetch(ctx.channelId).catch(() => null); + if (!channel?.isTextBased()) return; + + await channel.send({ embeds: [ctx.embed] }).catch(() => null); + }, + { context: { channelId: serverConnected.channelId, embed: embed.toJSON() } }, + ); + } + } + + /** + * Fetch a user blacklist from the database. + * @param hubId The hub ID to fetch the blacklist from. + * @param userId The ID of the blacklisted user. + */ + static async fetchUserBlacklist(hubId: string, userId: string) { + const userBlacklisted = await db.blacklistedUsers.findFirst({ + where: { userId, hubs: { some: { hubId } } }, + }); + return userBlacklisted; + } + + /** + * Fetch a server blacklist from the database. + * @param hubId The hub ID to fetch the blacklist from. + * @param serverId The ID of the blacklisted serverId. + */ + static async fetchServerBlacklist(hubId: string, serverId: string) { + const userBlacklisted = await db.blacklistedServers.findFirst({ + where: { serverId, hubs: { some: { hubId } } }, + }); + return userBlacklisted; + } + + /** + * Add a user to the blacklist. + * @param hubId The ID of the hub to add the blacklist to. + * @param userId The ID of the user to blacklist. + * @param reason The reason for the blacklist. + * @param expires The date or milliseconds after which the blacklist will expire. + * @returns The created blacklist. + */ + async addUserBlacklist(hubId: string, userId: string, reason: string, expires?: Date | number) { + const client = SuperClient.getInstance(); + const user = await client.users.fetch(userId); + if (typeof expires === 'number') expires = new Date(Date.now() + expires); + + const dbUser = await db.blacklistedUsers.findFirst({ where: { userId: user.id } }); + + const hubs = dbUser?.hubs.filter((i) => i.hubId !== hubId) || []; + hubs?.push({ expires: expires ?? null, reason, hubId }); + + const updatedUser = await db.blacklistedUsers.upsert({ + where: { + userId: user.id, + }, + update: { + username: user.username, + hubs: { set: hubs }, + }, + create: { + userId: user.id, + username: user.username, + hubs, + }, + }); + + return updatedUser; + } + + /** + * Add a server to the blacklist. + * @param serverId The ID of the server to blacklist. + * @param hubId The ID of the hub to add the blacklist to. + * @param reason The reason for the blacklist. + * @param expires The date after which the blacklist will expire. + * @returns The created blacklist. + */ + async addServerBlacklist(serverId: string, hubId: string, reason: string, expires?: Date) { + const client = SuperClient.getInstance(); + const guild = await client.fetchGuild(serverId); + if (!guild) return; + + const dbGuild = await db.blacklistedServers.upsert({ + where: { + serverId: guild.id, + }, + update: { + serverName: guild.name, + hubs: { push: { hubId, expires, reason } }, + }, + create: { + serverId: guild.id, + serverName: guild.name, + hubs: [{ hubId, expires, reason }], + }, + }); + + return dbGuild; + } +} diff --git a/src/structures/CommandManager.ts b/src/managers/CommandManager.ts similarity index 59% rename from src/structures/CommandManager.ts rename to src/managers/CommandManager.ts index dd83baaa..b3532c96 100644 --- a/src/structures/CommandManager.ts +++ b/src/managers/CommandManager.ts @@ -1,10 +1,9 @@ import { access, constants, readdirSync, statSync } from 'fs'; import { join, dirname } from 'path'; import Factory from '../Factory.js'; -import Logger from '../utils/Logger.js'; import BaseCommand, { commandsMap } from '../commands/BaseCommand.js'; import { emojis } from '../utils/Constants.js'; -import { CustomID } from './CustomID.js'; +import { CustomID } from '../structures/CustomID.js'; import { Interaction } from 'discord.js'; import { captureException } from '@sentry/node'; import { errorEmbed } from '../utils/Utils.js'; @@ -25,16 +24,62 @@ export default class CommandManager extends Factory { if (command?.autocomplete) command.autocomplete(interaction); } else if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) { - // const cooldown = this.client.commandCooldowns.get(interaction.user.id); - // if (cooldown && cooldown > Date.now()) { - // return await interaction.reply({ - // content: `You are on a cooldown! Use this command again .`, - // ephemeral: true, - // }); - // } + const command = this.client.commands.get(interaction.commandName); + if (!command) return; + + let remainingCooldown: number | undefined = undefined; + + if (interaction.isChatInputCommand()) { + const subcommandGroup = interaction.options.getSubcommandGroup(); + const subcommand = interaction.options.getSubcommand(); + + const baseCooldownName = `${interaction.user.id}-${interaction.commandName}`; + const subcommandKey = subcommandGroup + ? `${baseCooldownName}-${subcommandGroup}-${subcommand}` + : subcommand + ? `${baseCooldownName}-${subcommand}` + : baseCooldownName; + + remainingCooldown = this.client.commandCooldowns.getRemainingCooldown(subcommandKey); + + if (subcommand) { + const commandConstructor = command.constructor as typeof BaseCommand; + // this wont work for blacklist commands because of how that command is structured... + const subcommandClass = commandConstructor.subcommands?.get( + subcommandGroup || subcommand, + ); + + if (subcommandClass?.cooldown) { + this.client.commandCooldowns.setCooldown(subcommandKey, subcommandClass.cooldown); + } + } + } + else if (interaction.isContextMenuCommand()) { + remainingCooldown = this.client.commandCooldowns.getCooldown( + `${interaction.user.id}-${interaction.commandName}`, + ); + + // if command has cooldown, set cooldown for the user + if (command.cooldown) { + this.client.commandCooldowns.setCooldown( + `${interaction.user.id}-${interaction.commandName}`, + command.cooldown, + ); + } + } + + // check if command is in cooldown for the user + if (remainingCooldown) { + await interaction.reply({ + content: `${emojis.timeout} This command is on a cooldown! You can use it again: .`, + ephemeral: true, + }); + return; + } // run the command - this.client.commands.get(interaction.commandName)?.execute(interaction); + command?.execute(interaction); } else { const customId = CustomID.parseCustomId(interaction.customId); @@ -61,7 +106,7 @@ export default class CommandManager extends Factory { } } catch (e) { - Logger.error(e); + interaction.client.logger.error(e); captureException(e); } } diff --git a/src/structures/NetworkManager.ts b/src/managers/NetworkManager.ts similarity index 93% rename from src/structures/NetworkManager.ts rename to src/managers/NetworkManager.ts index 2ef6476f..ecb5abaa 100644 --- a/src/structures/NetworkManager.ts +++ b/src/managers/NetworkManager.ts @@ -17,6 +17,7 @@ import { REGEX, emojis } from '../utils/Constants.js'; import { censor } from '../utils/Profanity.js'; import { stripIndents } from 'common-tags'; import { HubSettingsBitField } from '../utils/BitFields.js'; +import { replaceLinks } from '../utils/Utils.js'; export interface NetworkMessage extends Message { censoredContent: string; @@ -111,6 +112,8 @@ export default class NetworkManager extends Factory { const { embed, censoredEmbed } = this.buildNetworkEmbed(message, { attachmentURL, referredContent, + embedCol: isNetworkMessage.embedColor as `#${string}` ?? undefined, + useNicknames: settings.has('UseNicknames'), }); const sendResult = allConnections.map(async (connection) => { @@ -174,7 +177,9 @@ export default class NetworkManager extends Factory { messageFormat = { embeds: replyEmbed ? [replyEmbed] : undefined, components: jumpButton ? [jumpButton] : undefined, - content: connection.profFilter ? message.censoredContent : message.content, + content: (connection.profFilter ? message.censoredContent : message.content) + + // append the attachment url if there is one + `${attachmentURL ? `\n${attachmentURL}` : ''}`, username: message.author.username, avatarURL: message.author.displayAvatarURL(), threadId: connection.parentId ? connection.channelId : undefined, @@ -227,7 +232,7 @@ export default class NetworkManager extends Factory { message.content.includes('dsc.gg') ) { message.reply( - 'Do not advertise or promote servers in the network. Set an invite in `/network manage` instead!', + 'Do not advertise or promote servers in the network. Set an invite in `/connection` instead!', ); return false; } @@ -241,7 +246,7 @@ export default class NetworkManager extends Factory { const antiSpamResult = this.runAntiSpam(message.author, 3); if (antiSpamResult) { - if (antiSpamResult.infractions >= 3) { + if (settings.has('SpamFilter') && antiSpamResult.infractions >= 3) { await blacklistManager.addUserBlacklist( hubId, message.author.id, @@ -251,7 +256,8 @@ export default class NetworkManager extends Factory { blacklistManager.scheduleRemoval('user', message.author.id, hubId, 60 * 5000); blacklistManager .notifyBlacklist( - message.author, + 'user', + message.author.id, hubId, new Date(Date.now() + 60 * 5000), 'Auto-blacklisted for spamming.', @@ -266,6 +272,11 @@ export default class NetworkManager extends Factory { message.reply('Please keep your message shorter than 1000 characters long.'); return false; } + + if (settings.has('HideLinks') && message.content.match(REGEX.LINKS)) { + message.content = replaceLinks(message.content); + } + // TODO allow multiple attachments when embeds can have multiple images const attachment = message.attachments.first(); const allowedTypes = ['image/gif', 'image/png', 'image/jpeg', 'image/jpg']; @@ -322,11 +333,18 @@ export default class NetworkManager extends Factory { public buildNetworkEmbed( message: NetworkMessage, - opts?: { attachmentURL?: string | null; embedCol?: HexColorString; referredContent?: string }, + opts?: { + attachmentURL?: string | null; + embedCol?: HexColorString; + referredContent?: string; + useNicknames?: boolean; + }, ): { embed: EmbedBuilder; censoredEmbed: EmbedBuilder } { const embed = new EmbedBuilder() .setAuthor({ - name: message.author.username, + name: opts?.useNicknames + ? message.member?.displayName || message.author.displayName + : message.author.username, iconURL: message.author.displayAvatarURL(), }) .setDescription(message.content || null) @@ -351,10 +369,12 @@ export default class NetworkManager extends Factory { .setDescription(message.censoredContent || null) .setFields( opts?.referredContent - ? [{ - name: 'Replying To:', - value: `> ${censor(opts.referredContent).replaceAll('\n', '\n> ')}` ?? 'Unknown.', - }] + ? [ + { + name: 'Replying To:', + value: `> ${censor(opts.referredContent).replaceAll('\n', '\n> ')}` ?? 'Unknown.', + }, + ] : [], ); diff --git a/src/scripts/network/buildEmbed.ts b/src/scripts/network/buildEmbed.ts index 192f6551..4ce1fc28 100644 --- a/src/scripts/network/buildEmbed.ts +++ b/src/scripts/network/buildEmbed.ts @@ -4,11 +4,14 @@ import { yesOrNoEmoji } from '../../utils/Utils.js'; import db from '../../utils/Db.js'; export async function buildEmbed(interaction: Interaction, channelId: string) { - const networkData = await db.connectedList.findFirst({ where: { channelId }, include: { hub: true } }); + const networkData = await db.connectedList.findFirst({ + where: { channelId }, + include: { hub: true }, + }); const { yes, no, enabled, disabled } = emojis; const invite = networkData?.invite - ? `Code: [\`${networkData.invite}\`](https://discord.gg/${networkData.invite})` + ? `Code: [\`${networkData.invite.replace('https://discord.gg/', '')}\`](${networkData.invite})` : 'Not Set.'; return new EmbedBuilder() @@ -19,12 +22,24 @@ export async function buildEmbed(interaction: Interaction, channelId: string) { { name: 'Hub', value: `${networkData?.hub?.name}`, inline: true }, { name: 'Invite', value: invite, inline: true }, { name: 'Connected', value: yesOrNoEmoji(networkData?.connected, yes, no), inline: true }, - { name: 'Compact', value: yesOrNoEmoji(networkData?.compact, enabled, disabled), inline: true }, - { name: 'Profanity Filter', value: yesOrNoEmoji(networkData?.profFilter, enabled, disabled), inline: true }, - { name: 'Embed Color', value: networkData?.embedColor ? `\`${networkData?.embedColor}\`` : no, inline: true }, + { + name: 'Compact', + value: yesOrNoEmoji(networkData?.compact, enabled, disabled), + inline: true, + }, + { + name: 'Profanity Filter', + value: yesOrNoEmoji(networkData?.profFilter, enabled, disabled), + inline: true, + }, + { + name: 'Embed Color', + value: networkData?.embedColor ? `\`${networkData?.embedColor}\`` : no, + inline: true, + }, ]) .setColor(colors.interchatBlue) .setThumbnail(interaction.guild?.iconURL() || interaction.client.user.avatarURL()) .setTimestamp() .setFooter({ text: 'Use to menu below to edit.' }); -} \ No newline at end of file +} diff --git a/src/scripts/network/onboarding.ts b/src/scripts/network/onboarding.ts index 8b03ed8a..dde14c57 100644 --- a/src/scripts/network/onboarding.ts +++ b/src/scripts/network/onboarding.ts @@ -10,7 +10,7 @@ import { AnySelectMenuInteraction, Collection, } from 'discord.js'; -import { colors, emojis, rulesEmbed } from '../../utils/Constants.js'; +import { colors, rulesEmbed } from '../../utils/Constants.js'; const onboardingInProgress = new Collection(); @@ -20,18 +20,10 @@ export async function showOnboarding( hubName: string, channelId: string, ephemeral = false, -) { +): Promise { // Check if server is already attempting to join a hub - if (onboardingInProgress.has(channelId)) { - const err = { - content: `${emojis.no} There has already been an attempt to join a hub in <#${channelId}>. Please wait for that to finish before trying again!`, - ephemeral, - }; - interaction.deferred || interaction.replied - ? interaction.followUp(err) - : interaction.reply(err); - return; - } + if (onboardingInProgress.has(channelId)) return 'in-progress'; + // Mark this as in-progress so server can't join twice onboardingInProgress.set(channelId, channelId); diff --git a/src/services/CooldownService.ts b/src/services/CooldownService.ts new file mode 100644 index 00000000..ea7e96e2 --- /dev/null +++ b/src/services/CooldownService.ts @@ -0,0 +1,42 @@ +import { Collection } from 'discord.js'; + +/** A service to manage cooldowns */ +export default class CooldownService { + private readonly cooldowns: Collection; + + constructor() { + this.cooldowns = new Collection(); + + setInterval(() => { + this.cooldowns.forEach((expires, key) => { + if (expires < Date.now()) this.cooldowns.delete(key); + }); + }, 60 * 1000); + } + + /** + * Set a cooldown + * @param id A unique id for the cooldown + * @param ms The duration of the cooldown in milliseconds + */ + public setCooldown(id: string, ms: number): void { + this.cooldowns.set(id, Date.now() + ms); + } + + /** Get a cooldown */ + public getCooldown(id: string) { + return this.cooldowns.get(id); + } + + /** Delete a cooldown */ + public deleteCooldown(id: string): void { + this.cooldowns.delete(id); + } + + /** Get the remaining cooldown in milliseconds */ + public getRemainingCooldown(id: string): number { + const cooldown = this.getCooldown(id); + if (!cooldown) return 0; + return cooldown - Date.now(); + } +} diff --git a/src/structures/Scheduler.ts b/src/services/SchedulerService.ts similarity index 66% rename from src/structures/Scheduler.ts rename to src/services/SchedulerService.ts index b767ac5f..b7c00ad1 100644 --- a/src/structures/Scheduler.ts +++ b/src/services/SchedulerService.ts @@ -5,6 +5,12 @@ export default class Scheduler { this.tasks = new Map(); } + /** + * Add a recurring task (interval) + * @param name The name of the task (must be unique) + * @param ms The interval in milliseconds + * @param task The function to execute every interval + */ addRecurringTask(name: string, ms: number | Date, task: () => void): void { if (this.tasks.has(name)) { throw new Error(`Task with name ${name} already exists.`); @@ -17,6 +23,12 @@ export default class Scheduler { this.tasks.set(name, { task, ms, timeout: intervalId }); } + /** + * Add a task (timeout) + * @param name The name of the task (must be unique) + * @param ms The interval in milliseconds + * @param task The function to execute after the interval + */ addTask(name: string, ms: number | Date, task: () => void): void { if (this.tasks.has(name)) { throw new Error(`Task with name ${name} already exists.`); @@ -29,6 +41,11 @@ export default class Scheduler { this.tasks.set(name, { task, ms, timeout }); } + /** + * Stop a task by name, can be used for both recurring and timeout tasks + * @param taskName The name of the task + * @returns true if task was stopped, undefined if task was not found + */ stopTask(taskName: string): boolean | undefined { const taskInfo = this.tasks.get(taskName); if (taskInfo) { @@ -37,6 +54,11 @@ export default class Scheduler { } return; } + + /** + * Stop all tasks + * @returns void + */ stopAllTasks(): void { this.tasks.forEach((taskInfo, taskName) => { clearInterval(taskInfo.timeout); @@ -44,6 +66,10 @@ export default class Scheduler { }); } + /** + * Get all currently running task names + * @returns An array of task names + */ get taskNames(): string[] { return Array.from(this.tasks.keys()); } diff --git a/src/structures/BlacklistManager.ts b/src/structures/BlacklistManager.ts deleted file mode 100644 index ebc47ee9..00000000 --- a/src/structures/BlacklistManager.ts +++ /dev/null @@ -1,199 +0,0 @@ -import db from '../utils/Db.js'; -import Logger from '../utils/Logger.js'; -import Scheduler from './Scheduler.js'; -import SuperClient from '../SuperClient.js'; -import { blacklistedServers, blacklistedUsers } from '@prisma/client'; -import { User, TextBasedChannel, EmbedBuilder } from 'discord.js'; -import { emojis, colors } from '../utils/Constants.js'; -import { captureException } from '@sentry/node'; - -export default class BlacklistManager { - private scheduler: Scheduler; - - constructor(scheduler: Scheduler) { - this.scheduler = scheduler; - } - - async removeBlacklist( - type: 'server', - hubId: string, - serverId: string, - ): Promise; - async removeBlacklist( - type: 'user', - hubId: string, - userId: string, - ): Promise; - async removeBlacklist( - type: 'user' | 'server', - hubId: string, - userOrServerId: string, - ) { - // FIXME find a better way to pass scheduler - this.scheduler.stopTask(`blacklist_${type}-${userOrServerId}`); - - if (type === 'user') { - return await db.blacklistedUsers.update({ - where: { - userId: userOrServerId, - hubs: { some: { hubId } }, - }, - data: { - hubs: { deleteMany: { where: { hubId } } }, - }, - }); - } - else { - return db.blacklistedServers.update({ - where: { - serverId: userOrServerId, - hubs: { some: { hubId } }, - }, - data: { - hubs: { deleteMany: { where: { hubId } } }, - }, - }); - } - } - - async scheduleRemoval( - type: 'server' | 'user', - userOrServerId: string, - hubId: string, - expires: Date | number, - ) { - let name: string; - let execute; - - if (type === 'server') { - name = `unblacklistServer-${userOrServerId}`; - execute = () => this.removeBlacklist('server', hubId, userOrServerId); - } - else { - name = `unblacklistUser-${userOrServerId}`; - execute = () => this.removeBlacklist('user', hubId, userOrServerId); - } - - this.scheduler.addTask(name, expires, execute); - } - - async notifyBlacklist( - userOrChannel: User | TextBasedChannel, - hubId: string, - expires?: Date, - reason: string = 'No reason provided.', - ) { - const hub = await db.hubs.findUnique({ where: { id: hubId } }); - const expireString = expires ? `` : 'Never'; - let embed: EmbedBuilder; - - if (userOrChannel instanceof User) { - embed = new EmbedBuilder() - .setTitle(emojis.blobFastBan + ' Blacklist Notification') - .setDescription(`You have been blacklisted from talking in hub **${hub?.name}**.`) - .setColor(colors.interchatBlue) - .setFields( - { name: 'Reason', value: reason, inline: true }, - { name: 'Expires', value: expireString, inline: true }, - ); - } - else { - embed = new EmbedBuilder() - .setTitle(emojis.blobFastBan + ' Blacklist Notification') - .setDescription(`This server has been blacklisted from talking in hub **${hub?.name}**.`) - .setColor(colors.interchatBlue) - .setFields( - { name: 'Reason', value: reason, inline: true }, - { name: 'Expires', value: expireString, inline: true }, - ); - } - - try { - await userOrChannel.send({ embeds: [embed] }).catch(() => null); - } - catch (e) { - Logger.error(e); - captureException(e); - } - } - - - static async fetchUserBlacklist(hubId: string, userId: string) { - const userBlacklisted = await db.blacklistedUsers.findFirst({ - where: { userId, hubs: { some: { hubId } } }, - }); - return userBlacklisted; - } - - - static async fetchServerBlacklist(hubId: string, serverId: string) { - const userBlacklisted = await db.blacklistedServers.findFirst({ - where: { serverId, hubs: { some: { hubId } } }, - }); - return userBlacklisted; - } - - - async addUserBlacklist( - hubId: string, - userId: string, - reason: string, - expires?: Date | number, - ) { - const client = SuperClient.getInstance(); - const user = await client.users.fetch(userId); - if (typeof expires === 'number') expires = new Date(Date.now() + expires); - - const dbUser = await db.blacklistedUsers.findFirst({ where: { userId: user.id } }); - - const hubs = dbUser?.hubs.filter((i) => i.hubId !== hubId) || []; - hubs?.push({ expires: expires ?? null, reason, hubId }); - - const updatedUser = await db.blacklistedUsers.upsert({ - where: { - userId: user.id, - }, - update: { - username: user.username, - hubs: { set: hubs }, - }, - create: { - userId: user.id, - username: user.username, - hubs, - }, - }); - - return updatedUser; - } - - - async addServerBlacklist( - serverId: string, - hubId: string, - reason: string, - expires?: Date, - ) { - const client = SuperClient.getInstance(); - const guild = await client.fetchGuild(serverId); - if (!guild) return; - - const dbGuild = await db.blacklistedServers.upsert({ - where: { - serverId: guild.id, - }, - update: { - serverName: guild.name, - hubs: { push: { hubId, expires, reason } }, - }, - create: { - serverId: guild.id, - serverName: guild.name, - hubs: [{ hubId, expires, reason }], - }, - }); - - return dbGuild; - } -} - diff --git a/src/structures/CustomID.ts b/src/structures/CustomID.ts index 1c75188e..1b7754eb 100644 --- a/src/structures/CustomID.ts +++ b/src/structures/CustomID.ts @@ -52,12 +52,12 @@ export class CustomID { } /** - * Parses a custom ID in the specified format. - * @param customId - The custom ID to parse. - * @returns ParsedCustomId - The parsed custom ID object. - */ + * Parses a custom ID in the specified format. + * @param customId - The custom ID to parse. + * @returns ParsedCustomId - The parsed custom ID object. + */ static parseCustomId(customId: string): ParsedCustomId { - // Split the customId by '&' + // Split the customId by '&' const split = customId.split('&'); // Extract prefix and postfix @@ -80,7 +80,6 @@ export class CustomID { return parsed; } - /** * Converts the CustomID instance to its string representation. * @returns string - The string representation of the CustomID. diff --git a/src/structures/NSFWDetection.ts b/src/structures/NSFWDetection.ts index 31c39a62..779b392f 100644 --- a/src/structures/NSFWDetection.ts +++ b/src/structures/NSFWDetection.ts @@ -1,15 +1,16 @@ import { REGEX } from '../utils/Constants.js'; -export declare const NSFW_CLASSES: { - [classId: number]: 'Drawing' | 'Hentai' | 'Neutral' | 'Porn' | 'Sexy'; -}; - export declare type predictionType = { - className: typeof NSFW_CLASSES[keyof typeof NSFW_CLASSES]; + className: 'Drawing' | 'Hentai' | 'Neutral' | 'Porn' | 'Sexy'; probability: number; }; export default class NSFWDetector { + /** + * Analyze an image URL and return the predictions + * @param url The image URL + * @returns The predictions + */ public async analyzeImage(url: string): Promise { if (!REGEX.STATIC_IMAGE_URL.test(url)) return null; const res = await fetch(`http://localhost:3000/nsfw?url=${url}`); @@ -17,13 +18,17 @@ export default class NSFWDetector { return res.status === 200 ? await res.json() : null; } + /** + * Check if the predictions are unsafe + * @param predictions The predictions to check + * @returns Whether the predictions are unsafe + */ public isUnsafeContent(predictions: predictionType[]): boolean { const safeCategories = ['Neutral', 'Drawing']; return predictions.some( (prediction) => - !safeCategories.includes(prediction.className) && - prediction.probability > 0.6, + !safeCategories.includes(prediction.className) && prediction.probability > 0.6, ); } -} \ No newline at end of file +} diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 66374445..2b6db2eb 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -1,11 +1,12 @@ import { ClusterClient } from 'discord-hybrid-sharding'; import { Collection, Snowflake } from 'discord.js'; import { Logger } from 'winston'; -import { Scheduler } from '../structures/Scheduler.ts'; +import { Scheduler } from '../services/SchedulerService.ts'; import NSFWClient from '../structures/NSFWDetection.ts'; import NetworkManager from '../structures/NetworkManager.ts'; -import BlacklistManager from '../structures/BlacklistManager.ts'; -import CommandManager from '../structures/CommandManager.ts'; +import BlacklistManager from '../managers/BlacklistManager.ts'; +import CommandManager from '../managers/CommandManager.ts'; +import CooldownService from '../services/CooldownService.ts'; type RemoveMethods = { [K in keyof T]: T[K] extends (...args: any[]) => any ? never : RemoveMethods; @@ -17,7 +18,7 @@ declare module 'discord.js' { readonly version: string; readonly development: boolean; readonly description: string; - readonly commandCooldowns: Collection; + readonly commandCooldowns: CooldownService; readonly reactionCooldowns: Collection; readonly cluster: ClusterClient; diff --git a/src/updater/ReactionUpdater.ts b/src/updater/ReactionUpdater.ts index c381b5b4..8860acc4 100644 --- a/src/updater/ReactionUpdater.ts +++ b/src/updater/ReactionUpdater.ts @@ -16,17 +16,15 @@ import { User, WebhookClient, } from 'discord.js'; -import { messageData, MessageDataChannelAndMessageIds } from '@prisma/client'; +import { MessageDataChannelAndMessageIds } from '@prisma/client'; import { sortReactions } from '../utils/Utils.js'; import { HubSettingsBitField } from '../utils/BitFields.js'; -import BlacklistManager from '../structures/BlacklistManager.js'; +import BlacklistManager from '../managers/BlacklistManager.js'; import { CustomID } from '../structures/CustomID.js'; -import { Interaction } from '../decorators/Interaction.js'; +import { RegisterInteractionHandler } from '../decorators/Interaction.js'; import { emojis } from '../utils/Constants.js'; import { stripIndents } from 'common-tags'; -type messageAndHubSettings = messageData & { hub: { settings: number } | null }; - export default class ReactionUpdater extends Factory { public async listenForReactions( reaction: MessageReaction | PartialMessageReaction, @@ -45,12 +43,18 @@ export default class ReactionUpdater extends Factory { include: { hub: { select: { settings: true } } }, }); - if (!messageInDb || !reaction.message.inGuild() || !ReactionUpdater.runChecks(messageInDb)) { + if ( + !messageInDb || + !reaction.message.inGuild() || + !messageInDb.hub || + !messageInDb.hubId || + !new HubSettingsBitField(messageInDb.hub.settings).has('Reactions') + ) { return; } const { userBlacklisted, serverBlacklisted } = await ReactionUpdater.checkBlacklists( - messageInDb, + messageInDb.hubId, reaction.message.guildId, user.id, ); @@ -81,7 +85,7 @@ export default class ReactionUpdater extends Factory { ReactionUpdater.updateReactions(messageInDb.channelAndMessageIds, dbReactions); } - @Interaction('reaction_') + @RegisterInteractionHandler('reaction_') async listenForReactionButton(interaction: ButtonInteraction | AnySelectMenuInteraction) { await interaction.deferUpdate(); @@ -96,12 +100,18 @@ export default class ReactionUpdater extends Factory { }, }); - if (!messageInDb || !interaction.inCachedGuild() || !ReactionUpdater.runChecks(messageInDb)) { + if ( + !messageInDb || + !interaction.inCachedGuild() || + !messageInDb.hub || + !messageInDb.hubId || + !new HubSettingsBitField(messageInDb.hub.settings).has('Reactions') + ) { return; } const { userBlacklisted, serverBlacklisted } = await ReactionUpdater.checkBlacklists( - messageInDb, + messageInDb.hubId, interaction.guildId, interaction.user.id, ); @@ -223,10 +233,12 @@ export default class ReactionUpdater extends Factory { if (interaction.isStringSelectMenu()) { // FIXME seems like emojiAlreadyReacted is getting mutated somewhere const action = emojiAlreadyReacted.includes(interaction.user.id) ? 'reacted' : 'unreacted'; - interaction.followUp({ - content: `You have ${action} with ${reactedEmoji}!`, - ephemeral: true, - }).catch(() => null); + interaction + .followUp({ + content: `You have ${action} with ${reactedEmoji}!`, + ephemeral: true, + }) + .catch(() => null); } // reflect the changes in the message's buttons @@ -313,27 +325,9 @@ export default class ReactionUpdater extends Factory { }); } - static runChecks(messageInDb: messageAndHubSettings) { - if ( - !messageInDb.hub || - !messageInDb.hubId || - !new HubSettingsBitField(messageInDb.hub.settings).has('Reactions') - ) { - return false; - } - - return true; - } - - static async checkBlacklists( - messageInDb: messageAndHubSettings | null, - guildId: string, - userId: string, - ) { - if (!messageInDb?.hubId) return { userBlacklisted: false, serverBlacklisted: false }; - - const userBlacklisted = await BlacklistManager.fetchUserBlacklist(messageInDb.hubId, userId); - const guildBlacklisted = await BlacklistManager.fetchUserBlacklist(messageInDb.hubId, guildId); + static async checkBlacklists(hubId: string, guildId: string, userId: string) { + const userBlacklisted = await BlacklistManager.fetchUserBlacklist(hubId, userId); + const guildBlacklisted = await BlacklistManager.fetchUserBlacklist(hubId, guildId); if (userBlacklisted || guildBlacklisted) { return { userBlacklisted, serverBlacklisted: guildBlacklisted }; } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 8bb4a5c2..f8545e86 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -24,7 +24,7 @@ export const REGEX = { /** ignores giphy and tenor */ LINKS: /https?:\/\/(?!tenor\.com|giphy\.com)\S+/g, /** matches imgur urls */ - IMGUR_LINKS: /(?:i\.imgur\.com\/(?!gallery|a|t|user)([^.]+)(?:\.\w+)?|imgur\.com\/(?!gallery|a|t|user)(\w+))/i, + IMGUR_LINKS: /(?:https?:\/\/)?(?:www\.)?imgur\.com\/(?:a\/|gallery\/)?([a-zA-Z0-9]+)(?:\.[a-zA-Z]+)?/i, }; export const StaffIds = ['597265261665714186', '442653948630007808', '689082827979227160']; @@ -40,6 +40,7 @@ export const URLs = { TOPGG_API: 'https://top.gg/api/bots/769921109209907241', VOTE: 'https://top.gg/bot/769921109209907241/vote', DOCS: 'https://discord-interchat.github.io/docs', + SUPPORT_INVITE: 'https://discord.gg/6bhXQynAPs', } as const; export const channels = { @@ -103,5 +104,5 @@ export const rulesEmbed = new EmbedBuilder() 6. **Respect Sensitive Topics:** Do not trivialize self-harm, suicide, violence, or other offensive topics. 7. **Report Concerns:** If you observe a violation of these rules, report it to the appropriate hub moderator or InterChat staff for further action. - Any questions? Join our [support server](https://discord.gg/6bhXQynAPs). + Any questions? Join our [support server](${URLs.SUPPORT_INVITE}). `); diff --git a/src/utils/RegisterCommands.ts b/src/utils/RegisterCommands.ts index a2b63a7e..57a8d77c 100644 --- a/src/utils/RegisterCommands.ts +++ b/src/utils/RegisterCommands.ts @@ -1,5 +1,5 @@ import Logger from './Logger.js'; -import CommandManager from '../structures/CommandManager.js'; +import CommandManager from '../managers/CommandManager.js'; import { REST, Routes } from 'discord.js'; import { CLIENT_ID, SUPPORT_SERVER_ID } from './Constants.js'; import { commandsMap } from '../commands/BaseCommand.js'; @@ -45,4 +45,4 @@ process.argv.forEach((arg) => { else { return; } -}); \ No newline at end of file +}); diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 0e77a70a..539c2e63 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -14,7 +14,7 @@ import { } from 'discord.js'; import { DeveloperIds, REGEX, StaffIds, SupporterIds, URLs, colors } from './Constants.js'; import { randomBytes } from 'crypto'; -import Scheduler from '../structures/Scheduler.js'; +import Scheduler from '../services/SchedulerService.js'; import db from './Db.js'; /** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ @@ -137,8 +137,8 @@ export function disableAllComponents( const jsonRow = row.toJSON(); jsonRow.components.forEach((component) => { !disableLinks && - (component.type === ComponentType.Button && - component.style === ButtonStyle.Link) + component.type === ComponentType.Button && + component.style === ButtonStyle.Link ? (component.disabled = false) // leave link buttons enabled : (component.disabled = true); }); @@ -159,9 +159,7 @@ export function replaceLinks(string: string, replaceText = '`[LINK HIDDEN]`') { } export function errorEmbed(description: string, color: ColorResolvable = colors.invisible) { - return new EmbedBuilder() - .setColor(color) - .setDescription(description.toString()); + return new EmbedBuilder().setColor(color).setDescription(description.toString()); } export function calculateAverageRating(ratings: number[]): number { @@ -175,15 +173,25 @@ export function calculateAverageRating(ratings: number[]): number { export async function checkAndFetchImgurUrl(url: string): Promise { const regex = REGEX.IMGUR_LINKS; const match = url.match(regex); - if (!match || !match[1] && !match[2]) return false; - const response = await fetch(`https://api.imgur.com/3/image/${match[1] || match[2]}`, { + if (!match || !match[1]) return false; + + const type = match[0].includes('/a/') || match[0].includes('/gallery/') ? 'gallery' : 'image'; + + const response = await fetch(`https://api.imgur.com/3/${type}/${match[1]}`, { headers: { Authorization: `Client-ID ${process.env.IMGUR_CLIENT_ID}`, }, }); const data = await response.json().catch(() => null); - if (!data || data?.data?.nsfw) return false; + if (!data || data?.data?.nsfw) { + return false; + } + // if means the image is an album or gallery + else if (data.data.cover) { + // refetch the cover image + return await checkAndFetchImgurUrl(`https://imgur.com/${data.data.cover}`); + } return data.data.link; -} \ No newline at end of file +} diff --git a/tests/HubCreate.test.ts b/tests/HubCreate.test.ts new file mode 100644 index 00000000..d69e54e1 --- /dev/null +++ b/tests/HubCreate.test.ts @@ -0,0 +1,108 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import Create from '../src/commands/slash/Main/hub/create.js'; +import { jest } from '@jest/globals'; +import { + ChatInputCommandInteraction, + ModalSubmitInteraction, + ModalBuilder, + ActionRowBuilder, + TextInputBuilder, +} from 'discord.js'; +import db from '../src/utils/Db.js'; + +describe('Create', () => { + let create: Create; + let interaction: ChatInputCommandInteraction; + let modalSubmitInteraction: ModalSubmitInteraction; + + beforeEach(() => { + create = new Create(); + interaction = {} as ChatInputCommandInteraction; + modalSubmitInteraction = { user: { id: '123' } } as ModalSubmitInteraction; + // @ts-ignore + db.hubs.create = jest.fn(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('execute', () => { + it('should show a modal with the correct components', async () => { + // @ts-ignore + interaction.deferReply = jest.fn().mockReturnValueOnce(interaction); + // @ts-ignore + interaction.editReply = jest.fn().mockReturnValueOnce(interaction); + // @ts-ignore + interaction.showModal = jest.fn().mockReturnValueOnce(interaction); + + const modalBuilderSpy = jest + .spyOn(ModalBuilder.prototype, 'setTitle') + .mockReturnValueOnce(new ModalBuilder()); + const addComponentsSpy = jest + .spyOn(ActionRowBuilder.prototype, 'addComponents') + .mockReturnValueOnce(new ActionRowBuilder()); + const setLabelSpy = jest + .spyOn(TextInputBuilder.prototype, 'setLabel') + .mockReturnValueOnce(new TextInputBuilder()); + const setPlaceholderSpy = jest + .spyOn(TextInputBuilder.prototype, 'setPlaceholder') + .mockReturnValueOnce(new TextInputBuilder()); + const setMinLengthSpy = jest + .spyOn(TextInputBuilder.prototype, 'setMinLength') + .mockReturnValueOnce(new TextInputBuilder()); + const setMaxLengthSpy = jest + .spyOn(TextInputBuilder.prototype, 'setMaxLength') + .mockReturnValueOnce(new TextInputBuilder()); + const setStyleSpy = jest + .spyOn(TextInputBuilder.prototype, 'setStyle') + .mockReturnValueOnce(new TextInputBuilder()); + const setCustomIdSpy = jest + .spyOn(TextInputBuilder.prototype, 'setCustomId') + .mockReturnValueOnce(new TextInputBuilder()); + + await create.execute(interaction); + + expect(modalBuilderSpy).toHaveBeenCalledWith('Create a hub'); + expect(addComponentsSpy).toHaveBeenCalledTimes(4); + expect(setLabelSpy).toHaveBeenCalledTimes(4); + expect(setPlaceholderSpy).toHaveBeenCalledTimes(4); + expect(setMinLengthSpy).toHaveBeenCalledWith(2); + expect(setMaxLengthSpy).toHaveBeenCalledTimes(4); + expect(setStyleSpy).toHaveBeenCalledTimes(4); + expect(setCustomIdSpy).toHaveBeenCalledTimes(4); + }); + }); + + it('should create a new hub if all inputs are valid', async () => { + // @ts-ignore + modalSubmitInteraction.deferReply = jest.fn().mockReturnValueOnce(modalSubmitInteraction); + // @ts-ignore + modalSubmitInteraction.editReply = jest.fn().mockReturnValueOnce(modalSubmitInteraction); + // @ts-ignore + modalSubmitInteraction.fields = { + getTextInputValue: (input: string) => { + if (input === 'name') return 'Test Hub'; + if (input === 'description') return 'Test Desc'; + if (input === 'icon') return 'https://i.imgur.com/uoRJPwW.gif'; + }, + }; + + await create.handleModals(modalSubmitInteraction); + + expect(db.hubs.create).toHaveBeenCalledWith({ + data: { + name: 'Test Hub', + description: 'Test Desc', + private: true, + ownerId: '123', + iconUrl: 'https://i.imgur.com/uoRJPwW.gif', + bannerUrl: undefined, + settings: 37, + }, + }); + + expect(modalSubmitInteraction.deferReply).toHaveBeenCalled(); + expect(modalSubmitInteraction.editReply).toHaveBeenCalled(); + }); +}); diff --git a/tests/ImgurLinks.test.ts b/tests/ImgurLinks.test.ts new file mode 100644 index 00000000..691bc675 --- /dev/null +++ b/tests/ImgurLinks.test.ts @@ -0,0 +1,39 @@ +// import { checkAndFetchImgurUrl } from '../src/utils/Utils.js'; + +describe('checkAndFetchImgurUrl', () => { + it('should return false for any other URL', async () => { + const url = 'https://example.com'; + const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); + expect(result).toBe(false); + }); + + it('should return false for Imgur URLs with invalid IDs', async () => { + const url = 'https://imgur.com/invalid'; + const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); + expect(result).toBe(false); + }); + + it('should return cover image if its a gallery', async () => { + const url = 'https://imgur.com/gallery/Xqre9nv'; + const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); + expect(result).toBe('https://i.imgur.com/lGeNJwz.jpg'); + }); + + it('should return cover image if its an album', async () => { + const url = 'https://imgur.com/a/Xqre9nv'; + const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); + expect(result).toBe('https://i.imgur.com/lGeNJwz.jpg'); + }); + + it('should return Imgur URLs for valid Imgur URLs', async () => { + const url = 'https://imgur.com/uoRJPwW'; + const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); + expect(result).toBe('https://i.imgur.com/uoRJPwW.gif'); + }); + + it('should return Imgur URL for valid i.imgur.com URLs', async () => { + const url = 'https://i.imgur.com/uoRJPwW.gif'; + const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); + expect(result).toBe(url); + }); +}); From 5edce549641a388ffa06a42b47e41e8a04e71dce Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sun, 22 Oct 2023 18:51:43 +0530 Subject: [PATCH 06/13] add comments --- src/SuperClient.ts | 12 +++++++ src/decorators/Interaction.ts | 2 +- src/managers/CommandManager.ts | 8 ++--- src/managers/NetworkManager.ts | 57 +++++++++++++++++++++++++++++-- src/scripts/network/onboarding.ts | 9 ++++- src/services/SchedulerService.ts | 31 ++++++++--------- src/typings/index.d.ts | 2 +- src/updater/ReactionUpdater.ts | 52 +++++++++++++++++++++++++--- 8 files changed, 141 insertions(+), 32 deletions(-) diff --git a/src/SuperClient.ts b/src/SuperClient.ts index f58e3b57..22b0b644 100644 --- a/src/SuperClient.ts +++ b/src/SuperClient.ts @@ -73,6 +73,10 @@ export default abstract class SuperClient extends Client { }); } + /** + * Initializes the SuperClient instance. + * Sets the instance to the current object and initializes Sentry error monitoring and handling if not in development mode. + */ protected init() { SuperClient.self = this; @@ -86,6 +90,9 @@ export default abstract class SuperClient extends Client { } } + /** + * Returns the instance of the SuperClient class. + */ public static getInstance(): SuperClient { return this.self; } @@ -93,6 +100,11 @@ export default abstract class SuperClient extends Client { // eslint-disable-next-line @typescript-eslint/no-explicit-any resolveEval = (value: any[]): T | undefined => value?.find((res) => !!res); + /** + * Fetches a guild by its ID from the cache. + * @param guildId The ID of the guild to fetch. + * @returns The fetched guild **without any methods**, or undefined if the guild is not found. + */ async fetchGuild(guildId: Snowflake): Promise | undefined> { const fetch = await this.cluster.broadcastEval( (client, guildID) => client.guilds.cache.get(guildID), diff --git a/src/decorators/Interaction.ts b/src/decorators/Interaction.ts index 50ad4b9f..954097ab 100644 --- a/src/decorators/Interaction.ts +++ b/src/decorators/Interaction.ts @@ -5,7 +5,7 @@ export type InteractionFunction = ( interaction: MessageComponentInteraction | ModalSubmitInteraction, ) => Promise | void; -// Decorator function to call a specified method when an interaction is created (ie. interactionCreate event) +/** Decorator function to call a specified method when an interaction is created (ie. interactionCreate event) */ export function RegisterInteractionHandler(customId: string): MethodDecorator { return function( targetClass: Record, diff --git a/src/managers/CommandManager.ts b/src/managers/CommandManager.ts index b3532c96..2419db08 100644 --- a/src/managers/CommandManager.ts +++ b/src/managers/CommandManager.ts @@ -16,8 +16,8 @@ export default class CommandManager extends Factory { return this.client.commands; } - /** `InteractionCreate` event handler */ - async handleInteraction(interaction: Interaction) { + /** Handle interactions from the `InteractionCreate` event */ + async handleInteraction(interaction: Interaction): Promise { try { if (interaction.isAutocomplete()) { const command = this.client.commands.get(interaction.commandName); @@ -112,8 +112,8 @@ export default class CommandManager extends Factory { } /** - * Loads all commands from the Commands directory - * Commands are automatically added to the `clientCommands` map + * Recursively loads all command files from the given directory and its subdirectories. + * @param commandDir The directory to load command files from. */ static async loadCommandFiles(commandDir = join(__dirname, '..', 'commands')): Promise { const files = readdirSync(commandDir); diff --git a/src/managers/NetworkManager.ts b/src/managers/NetworkManager.ts index ecb5abaa..9c261868 100644 --- a/src/managers/NetworkManager.ts +++ b/src/managers/NetworkManager.ts @@ -43,6 +43,10 @@ const MAX_STORE = 3; export default class NetworkManager extends Factory { private antiSpamMap = new Collection(); + /** + * Handles a network message by running checks, fetching relevant data, and sending the message to all connections in the network. + * @param message The network message to handle. + */ public async handleNetworkMessage(message: NetworkMessage) { const isNetworkMessage = await db.connectedList.findFirst({ where: { channelId: message.channel.id, connected: true }, @@ -208,6 +212,13 @@ export default class NetworkManager extends Factory { await this.storeMessageData(message, await Promise.all(sendResult), isNetworkMessage.hubId); } + /** + * Runs various checks on a message to determine if it can be sent in the network. + * @param message - The message to check. + * @param settings - The settings for the network. + * @param hubId - The ID of the hub the message is being sent in. + * @returns A boolean indicating whether the message passed all checks. + */ public async runChecks( message: Message, settings: HubSettingsBitField, @@ -296,6 +307,13 @@ export default class NetworkManager extends Factory { return true; } + /** + * Retrieves the content of a referred message, which can be either the message's text content or the description of its first embed. + * If the referred message has no content, returns a default message indicating that the original message contains an attachment. + * If the referred message's content exceeds 1000 characters, truncates it and appends an ellipsis. + * @param referredMessage The message being referred to. + * @returns The content of the referred message. + */ public async getReferredContent(referredMessage: Message) { let referredContent = referredMessage.content || referredMessage.embeds[0]?.description; @@ -309,6 +327,11 @@ export default class NetworkManager extends Factory { return referredContent; } + /** + * Returns the URL of an attachment in a message, if it exists. + * @param message The message to search for an attachment URL. + * @returns The URL of the attachment, or null if no attachment is found. + */ public async getAttachmentURL(message: Message) { // Tenor Gifs / Image URLs const URLMatch = message.content.match(REGEX.STATIC_IMAGE_URL); @@ -331,6 +354,16 @@ export default class NetworkManager extends Factory { return null; } + /** + * Builds an embed for a network message. + * @param message The network message to build the embed for. + * @param opts Optional parameters for the embed. + * @param opts.attachmentURL The URL of the attachment to include in the embed. + * @param opts.embedCol The color of the embed. + * @param opts.referredContent The content of the message being replied to. + * @param opts.useNicknames Whether to use nicknames instead of usernames in the embed. + * @returns An object containing the built EmbedBuilder and its censored version. + */ public buildNetworkEmbed( message: NetworkMessage, opts?: { @@ -382,9 +415,10 @@ export default class NetworkManager extends Factory { } /** - * Stores message in the db after it has been sent to the network - * And disconnects the network if the webhook is invalid - * */ + * Stores message data in the database and updates the connectedList based on the webhook status. + * @param channelAndMessageIds The result of sending the message to multiple channels. + * @param hubId The ID of the hub to connect the message data to. + */ protected async storeMessageData( message: Message, channelAndMessageIds: NetworkWebhookSendResult[], @@ -435,6 +469,12 @@ export default class NetworkManager extends Factory { } } + /** + * Runs the anti-spam mechanism for a given user. + * @param author - The user to run the anti-spam mechanism for. + * @param maxInfractions - The maximum number of infractions before the user is blacklisted. + * @returns The user's anti-spam data if they have reached the maximum number of infractions, otherwise undefined. + */ runAntiSpam(author: User, maxInfractions = MAX_STORE) { const userInCol = this.antiSpamMap.get(author.id); const currentTimestamp = Date.now(); @@ -476,6 +516,11 @@ export default class NetworkManager extends Factory { } } + /** + * Sets spam timers for a given user. + * @param userId - The ID of the user to set spam timers for. + * @returns void + */ setSpamTimers(userId: string): void { const five_min = 60 * 5000; const userInCol = this.antiSpamMap.get(userId); @@ -511,6 +556,12 @@ export default class NetworkManager extends Factory { return await db.connectedList.create({ data }); } + /** + * Sends a message to all connections in a hub's network. + * @param hubId The ID of the hub to send the message to. + * @param message The message to send. Can be a string or a MessageCreateOptions object. + * @returns A array of the responses from each connection's webhook. + */ async sendToNetwork(hubId: string, message: string | MessageCreateOptions) { const connections = await this.fetchHubNetworks({ hubId }); diff --git a/src/scripts/network/onboarding.ts b/src/scripts/network/onboarding.ts index dde14c57..a1a3c6af 100644 --- a/src/scripts/network/onboarding.ts +++ b/src/scripts/network/onboarding.ts @@ -14,7 +14,14 @@ import { colors, rulesEmbed } from '../../utils/Constants.js'; const onboardingInProgress = new Collection(); -/* Make user accept and understand important info on first setup */ +/** + * Shows the onboarding message for a hub in the specified channel. + * @param interaction - The interaction that triggered the onboarding message. + * @param hubName - The name of the hub to join. + * @param channelId - The ID of the channel to show the onboarding message in. + * @param ephemeral - Whether the onboarding message should only be visible to the user who triggered it. + * @returns A Promise that resolves to `true` if the user accepts the onboarding message, `false` if they cancel it, or `'in-progress'` if onboarding is already in progress for the channel. + */ export async function showOnboarding( interaction: ChatInputCommandInteraction | AnySelectMenuInteraction | ButtonInteraction, hubName: string, diff --git a/src/services/SchedulerService.ts b/src/services/SchedulerService.ts index b7c00ad1..e71ce7cc 100644 --- a/src/services/SchedulerService.ts +++ b/src/services/SchedulerService.ts @@ -6,10 +6,11 @@ export default class Scheduler { } /** - * Add a recurring task (interval) - * @param name The name of the task (must be unique) - * @param ms The interval in milliseconds - * @param task The function to execute every interval + * Adds a recurring task to the scheduler. + * @param taskName - The name of the task to add. + * @param interval - The interval at which the task should be executed, in milliseconds or as a Date object. + * @param task - The function to execute as the task. + * @throws An error if a task with the same name already exists. */ addRecurringTask(name: string, ms: number | Date, task: () => void): void { if (this.tasks.has(name)) { @@ -24,17 +25,17 @@ export default class Scheduler { } /** - * Add a task (timeout) - * @param name The name of the task (must be unique) - * @param ms The interval in milliseconds - * @param task The function to execute after the interval + * Adds a new task to the scheduler. + * @param name - The name of the task. + * @param ms - The number of milliseconds after which the task should be executed, or a Date object representing the time at which the task should be executed. + * @param task - The function to be executed when the task is run. + * @throws An error if a task with the same name already exists. */ addTask(name: string, ms: number | Date, task: () => void): void { if (this.tasks.has(name)) { throw new Error(`Task with name ${name} already exists.`); } - // if interval is instance of Date, convert it to milliseconds ms = ms instanceof Date ? ms.getTime() - Date.now() : ms; const timeout = setTimeout(task, ms); @@ -50,25 +51,21 @@ export default class Scheduler { const taskInfo = this.tasks.get(taskName); if (taskInfo) { clearInterval(taskInfo.timeout); + clearTimeout(taskInfo.timeout); return this.tasks.delete(taskName); } return; } /** - * Stop all tasks - * @returns void + * Stop all currently running tasks */ stopAllTasks(): void { - this.tasks.forEach((taskInfo, taskName) => { - clearInterval(taskInfo.timeout); - this.tasks.delete(taskName); - }); + this.tasks.forEach((_, taskName) => this.stopTask(taskName)); } /** - * Get all currently running task names - * @returns An array of task names + * Returns an array of currently running task names. */ get taskNames(): string[] { return Array.from(this.tasks.keys()); diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 2b6db2eb..b032aac6 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -9,7 +9,7 @@ import CommandManager from '../managers/CommandManager.ts'; import CooldownService from '../services/CooldownService.ts'; type RemoveMethods = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? never : RemoveMethods; + [K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? never : RemoveMethods; }; declare module 'discord.js' { diff --git a/src/updater/ReactionUpdater.ts b/src/updater/ReactionUpdater.ts index 8860acc4..c89c8bb5 100644 --- a/src/updater/ReactionUpdater.ts +++ b/src/updater/ReactionUpdater.ts @@ -26,6 +26,9 @@ import { emojis } from '../utils/Constants.js'; import { stripIndents } from 'common-tags'; export default class ReactionUpdater extends Factory { + /** + * Listens for reactions on a message and updates the database with the new reaction data. + */ public async listenForReactions( reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser, @@ -85,6 +88,10 @@ export default class ReactionUpdater extends Factory { ReactionUpdater.updateReactions(messageInDb.channelAndMessageIds, dbReactions); } + + /** + * Listens for a reaction button or select menu interaction and updates the reactions accordingly. + * */ @RegisterInteractionHandler('reaction_') async listenForReactionButton(interaction: ButtonInteraction | AnySelectMenuInteraction) { await interaction.deferUpdate(); @@ -246,7 +253,13 @@ export default class ReactionUpdater extends Factory { } } - // making methods static so we can use them in the decorator + /* static methods so we can call them within the decorator */ + + /** + * Updates reactions on messages in multiple channels. + * @param channelAndMessageIds An array of objects containing channel and message IDs. + * @param reactions An object containing the reactions data. + */ static async updateReactions( channelAndMessageIds: MessageDataChannelAndMessageIds[], reactions: { [key: string]: string[] }, @@ -325,6 +338,13 @@ export default class ReactionUpdater extends Factory { }); } + /** + * Checks if a user or server is blacklisted in a given hub. + * @param hubId - The ID of the hub to check in. + * @param guildId - The ID of the guild to check for blacklist. + * @param userId - The ID of the user to check for blacklist. + * @returns An object containing whether the user and/or server is blacklisted in the hub. + */ static async checkBlacklists(hubId: string, guildId: string, userId: string) { const userBlacklisted = await BlacklistManager.fetchUserBlacklist(hubId, userId); const guildBlacklisted = await BlacklistManager.fetchUserBlacklist(hubId, guildId); @@ -335,9 +355,23 @@ export default class ReactionUpdater extends Factory { return { userBlacklisted: false, serverBlacklisted: false }; } + /** + * Adds a user ID to the array of user IDs for a given emoji in the reactionArr object. + * @param reactionArr - The object containing arrays of user IDs for each emoji. + * @param userId - The ID of the user to add to the array. + * @param emoji - The emoji to add the user ID to. + */ static addReaction(reactionArr: { [key: string]: Snowflake[] }, userId: string, emoji: string) { reactionArr[emoji].push(userId); } + + /** + * Removes a user's reaction from the reaction array. + * @param reactionArr - The reaction array to remove the user's reaction from. + * @param userId - The ID of the user whose reaction is to be removed. + * @param emoji - The emoji of the reaction to be removed. + * @returns The updated reaction array after removing the user's reaction. + */ static removeReaction( reactionArr: { [key: string]: Snowflake[] }, userId: string, @@ -348,11 +382,19 @@ export default class ReactionUpdater extends Factory { return reactionArr; } + /** + * Sorts the reactions object based on the reaction counts. + * @param reactions - The reactions object to be sorted. + * @returns The sorted reactions object in the form of an array. + * The array is sorted in descending order based on the length of the reaction arrays. + * Each element of the array is a tuple containing the reaction and its corresponding array of user IDs. + * + * ### Example: + * ```js + * [ [ '👎', ['1020193019332334'] ], [ '👍', ['1020193019332334'] ] ] + * ``` + */ static sortReactions(reactions: { [key: string]: string[] }) { - // Sort the array based on the reaction counts - /* { '👍': ['10201930193'], '👎': ['10201930193'] } // before Object.entries - => [ [ '👎', ['10201930193'] ], [ '👍', ['10201930193'] ] ] // after Object.entries - */ return Object.entries(reactions).sort((a, b) => b[1].length - a[1].length); } } From 17ffafa103766f578676919eb0795f11ec4fc478 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:36:42 +0530 Subject: [PATCH 07/13] fix more bugs and add cooldowns --- package.json | 2 +- prisma/schema.prisma | 38 ++++++++--------- src/commands/slash/Main/hub/browse.ts | 13 ++++-- src/commands/slash/Main/hub/index.ts | 14 ++++++- src/commands/slash/Main/hub/moderator.ts | 3 +- src/commands/slash/Staff/purge.ts | 53 +++++++++++++++--------- src/managers/CommandManager.ts | 4 +- src/managers/NetworkManager.ts | 7 ++-- src/utils/Pagination.ts | 5 ++- 9 files changed, 87 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 48e49d50..c3a8c042 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "node .", "build": "tsc --build && npm run sentry:sourcemaps", "build:dev": "tsc --build", - "dev": "tsc-watch --outDir ./build --onSuccess \"node .\"", + "dev": "tsc-watch --outDir ./build --onSuccess \"node --trace-warnings .\"", "register:commands": "node build/utils/RegisterCommands.js", "release": "standard-version", "lint": "eslint --cache --fix .", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 03d630e0..07d0fa7a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -55,15 +55,15 @@ type hubBlacklist { } model blacklistedServers { - id String @id @default(auto()) @map("_id") @db.ObjectId - serverId String @unique + id String @id @default(auto()) @map("_id") @db.ObjectId + serverId String @unique serverName String hubs hubBlacklist[] } model blacklistedUsers { - id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @unique + id String @id @default(auto()) @map("_id") @db.ObjectId + userId String @unique username String hubs hubBlacklist[] } @@ -85,22 +85,22 @@ model connectedList { } model hubs { - id String @id @default(auto()) @map("_id") @db.ObjectId - name String @unique - description String - rating HubRating[] - ownerId String - iconUrl String - bannerUrl String? - private Boolean @default(true) - createdAt DateTime @default(now()) + id String @id @default(auto()) @map("_id") @db.ObjectId + name String @unique + description String + rating HubRating[] + ownerId String + iconUrl String + bannerUrl String? + private Boolean @default(true) + createdAt DateTime @default(now()) // settings are stored as a number, each bit is a setting - settings Int + settings Int // all the stuff below is relations to other collections - invites hubInvites[] - messages messageData[] - moderators HubModerator[] - connections connectedList[] + invites hubInvites[] + messages messageData[] + moderators HubModerator[] + connections connectedList[] // approved Boolean @default(false) // official Boolean @default(false) } @@ -117,7 +117,7 @@ model messageData { id String @id @default(auto()) @map("_id") @db.ObjectId authorId String channelAndMessageIds MessageDataChannelAndMessageIds[] - reference MessageDataReference? + referenceDocId String? @db.ObjectId serverId String timestamp DateTime reactions Json // eg. {"👎": ["9893820930928", "39283902803982"]} "emoji": userId[] basically diff --git a/src/commands/slash/Main/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts index 6a023113..102defff 100644 --- a/src/commands/slash/Main/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -78,7 +78,12 @@ export default class Browse extends Hub { .count({ where: { hubId: hub.id, connected: true } }) .catch(() => 0); - return Browse.createHubListingsEmbed(hub, connections); + const lastMessage = await db.messageData.findFirst({ + where: { hubId: hub.id }, + orderBy: { timestamp: 'desc' }, + }); + + return Browse.createHubListingsEmbed(hub, connections, lastMessage?.timestamp); }), ); @@ -357,20 +362,20 @@ export default class Browse extends Hub { } // utils - static createHubListingsEmbed(hub: hubs, connections?: number) { + static createHubListingsEmbed(hub: hubs, connections?: number, lastMessage?: Date) { return new EmbedBuilder() .setDescription( stripIndents` ### ${hub.name} ${hub.description} - **Rating:** ${ - hub.rating?.length > 0 + **Rating:** ${hub.rating?.length > 0 ? '⭐'.repeat(calculateAverageRating(hub.rating.map((hr) => hr.rating))) : '-' } **Connections:** ${connections ?? 'Unknown.'} **Created At:** + **Last Message:** ${lastMessage ? `` : '-'} `, ) .setColor('Random') diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts index bb981f48..1828913e 100644 --- a/src/commands/slash/Main/hub/index.ts +++ b/src/commands/slash/Main/hub/index.ts @@ -44,7 +44,19 @@ export default class Hub extends BaseCommand { choices: [ { name: 'Most Active', - value: 'most-active', + value: 'active', + }, + { + name: 'Most Popular', + value: 'popular', + }, + { + name: 'Most Connections', + value: 'connections', + }, + { + name: 'Recently Created', + value: 'recent', }, ], }, diff --git a/src/commands/slash/Main/hub/moderator.ts b/src/commands/slash/Main/hub/moderator.ts index 6f49ac83..fd64b1f9 100644 --- a/src/commands/slash/Main/hub/moderator.ts +++ b/src/commands/slash/Main/hub/moderator.ts @@ -73,7 +73,8 @@ export default class Moderator extends Hub { moderators: { deleteMany: { where: { userId: user.id } } }, }, }); - interaction.reply(`Removed hub moderator ${user} from **${hub.name}**!`); + + await interaction.reply(`Removed hub moderator ${user} from **${hub.name}**!`); break; } diff --git a/src/commands/slash/Staff/purge.ts b/src/commands/slash/Staff/purge.ts index 9730e936..b32c044f 100644 --- a/src/commands/slash/Staff/purge.ts +++ b/src/commands/slash/Staff/purge.ts @@ -153,10 +153,20 @@ export default class Purge extends BaseCommand { } case 'replies': { const messageId = interaction.options.getString('replied-to', true); - messagesInDb = await messageData.findMany({ - where: { hubId: channelInHub.hubId, reference: { is: { messageId } } }, - take: limit, + const originalMsg = await messageData.findFirst({ + where: { channelAndMessageIds: { some: { messageId } } }, }); + + + messagesInDb = originalMsg + ? await messageData.findMany({ + where: { + hubId: channelInHub.hubId, + referenceDocId: originalMsg.id, + }, + take: limit, + }) + : []; break; } case 'any': @@ -173,7 +183,8 @@ export default class Purge extends BaseCommand { if (!messagesInDb || messagesInDb.length < 1) { return await interaction.reply({ - content: 'Messages to purge not found; messages sent over 24 hours ago have been automatically removed.', + content: + 'Messages to purge not found; messages sent over 24 hours ago have been automatically removed.', ephemeral: true, }); } @@ -185,27 +196,29 @@ export default class Purge extends BaseCommand { where: { hubId: channelInHub.hubId, connected: true }, }); - const promiseResults = allNetworks.map(async (network) => { try { // TODO: Fine a better way to do this // because we are doing this in all the shards, which is double the work - const evalRes = await interaction.client.cluster.broadcastEval(async (client, ctx) => { - const channel = await client.channels.fetch(ctx.channelId); + const evalRes = await interaction.client.cluster.broadcastEval( + async (client, ctx) => { + const channel = await client.channels.fetch(ctx.channelId); - if (channel?.type === 0 || channel?.isThread()) { - const messageIds = ctx.messagesInDb.flatMap((dbMsg) => - dbMsg.channelAndMessageIds - .filter(({ channelId }) => channelId === channel.id) - .map(({ messageId }) => messageId), - ); + if (channel?.type === 0 || channel?.isThread()) { + const messageIds = ctx.messagesInDb.flatMap((dbMsg) => + dbMsg.channelAndMessageIds + .filter(({ channelId }) => channelId === channel.id) + .map(({ messageId }) => messageId), + ); - if (messageIds.length < 1) return []; + if (messageIds.length < 1) return []; - await channel.bulkDelete(messageIds); - return messageIds; - } - }, { context: { channelId: network.channelId, messagesInDb } }); + await channel.bulkDelete(messageIds); + return messageIds; + } + }, + { context: { channelId: network.channelId, messagesInDb } }, + ); return interaction.client.resolveEval(evalRes) || []; } @@ -226,7 +239,9 @@ export default class Purge extends BaseCommand { stripIndents` ### ${emojis.delete} Purge Results - Finished purging from **${allNetworks.length}** networks in \`${msToReadable(performance.now() - startTime)}\`. + Finished purging from **${allNetworks.length}** networks in \`${msToReadable( + performance.now() - startTime, +)}\`. `, ) .addFields([ diff --git a/src/managers/CommandManager.ts b/src/managers/CommandManager.ts index 2419db08..4a45b5c2 100644 --- a/src/managers/CommandManager.ts +++ b/src/managers/CommandManager.ts @@ -30,8 +30,8 @@ export default class CommandManager extends Factory { let remainingCooldown: number | undefined = undefined; if (interaction.isChatInputCommand()) { - const subcommandGroup = interaction.options.getSubcommandGroup(); - const subcommand = interaction.options.getSubcommand(); + const subcommandGroup = interaction.options.getSubcommandGroup(false); + const subcommand = interaction.options.getSubcommand(false); const baseCooldownName = `${interaction.user.id}-${interaction.commandName}`; const subcommandKey = subcommandGroup diff --git a/src/managers/NetworkManager.ts b/src/managers/NetworkManager.ts index 9c261868..a91facbe 100644 --- a/src/managers/NetworkManager.ts +++ b/src/managers/NetworkManager.ts @@ -12,7 +12,7 @@ import { } from 'discord.js'; import Factory from '../Factory.js'; import db from '../utils/Db.js'; -import { Prisma, connectedList, hubs } from '@prisma/client'; +import { Prisma, connectedList, hubs, messageData } from '@prisma/client'; import { REGEX, emojis } from '../utils/Constants.js'; import { censor } from '../utils/Profanity.js'; import { stripIndents } from 'common-tags'; @@ -209,7 +209,7 @@ export default class NetworkManager extends Factory { if (!attachment) message.delete().catch(() => null); // store the message in the db - await this.storeMessageData(message, await Promise.all(sendResult), isNetworkMessage.hubId); + await this.storeMessageData(message, await Promise.all(sendResult), isNetworkMessage.hubId, referenceInDb); } /** @@ -423,6 +423,7 @@ export default class NetworkManager extends Factory { message: Message, channelAndMessageIds: NetworkWebhookSendResult[], hubId: string, + dbReference?: messageData | null, ): Promise { const messageDataObj: { channelId: string; messageId: string }[] = []; const invalidWebhookURLs: string[] = []; @@ -454,7 +455,7 @@ export default class NetworkManager extends Factory { timestamp: message.createdAt, authorId: message.author.id, serverId: message.guild.id, - reference: message.reference, + referenceDocId: dbReference?.id, reactions: {}, }, }); diff --git a/src/utils/Pagination.ts b/src/utils/Pagination.ts index 39a5cb59..50952c26 100644 --- a/src/utils/Pagination.ts +++ b/src/utils/Pagination.ts @@ -5,9 +5,10 @@ import { EmbedBuilder, CommandInteraction, ComponentType, + MessageComponentInteraction, + MessageActionRowComponentBuilder, } from 'discord.js'; import { emojis } from './Constants.js'; -import { MessageActionRowComponentBuilder } from 'discord.js'; export interface PaginatorOptions { /** Number in milliseconds */ @@ -25,7 +26,7 @@ export interface PaginatorOptions { } export async function paginate( - interaction: CommandInteraction, + interaction: CommandInteraction | MessageComponentInteraction, pages: EmbedBuilder[], options?: PaginatorOptions, ) { From 8605f7006d623cd7dbc5face0d1dedf4cc7f0730 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:18:06 +0530 Subject: [PATCH 08/13] store blacklist mod id, rename Message Info/Report cmd --- prisma/schema.prisma | 7 +- src/commands/context-menu/blacklist.ts | 2 + src/commands/context-menu/messageInfo.ts | 2 +- src/commands/slash/Main/blacklist/list.ts | 92 +++++++++++++-------- src/commands/slash/Main/blacklist/server.ts | 8 +- src/commands/slash/Main/blacklist/user.ts | 2 +- src/commands/slash/Main/hub/browse.ts | 41 ++++++--- src/index.ts | 24 +++--- src/managers/BlacklistManager.ts | 30 +++++-- src/managers/NetworkManager.ts | 17 ++-- src/utils/JSON/emojis.json | 10 ++- 11 files changed, 155 insertions(+), 80 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 07d0fa7a..2a9a22a8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -49,9 +49,10 @@ type HubModerator { // } type hubBlacklist { - reason String - expires DateTime? - hubId String @db.ObjectId + reason String + expires DateTime? + moderatorId String? + hubId String @db.ObjectId } model blacklistedServers { diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts index 6bc948b8..b0b2b1b8 100644 --- a/src/commands/context-menu/blacklist.ts +++ b/src/commands/context-menu/blacklist.ts @@ -185,6 +185,7 @@ export default class Blacklist extends BaseCommand { messageInDb.hubId, messageInDb.authorId, reason, + interaction.user.id, expires, ); @@ -210,6 +211,7 @@ export default class Blacklist extends BaseCommand { messageInDb.serverId, messageInDb.hubId, reason, + interaction.user.id, expires, ); diff --git a/src/commands/context-menu/messageInfo.ts b/src/commands/context-menu/messageInfo.ts index 64f1d080..2741c15a 100644 --- a/src/commands/context-menu/messageInfo.ts +++ b/src/commands/context-menu/messageInfo.ts @@ -22,7 +22,7 @@ import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; export default class MessageInfo extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { type: ApplicationCommandType.Message, - name: 'Message Info', + name: 'Message Info/Report', dm_permission: false, }; diff --git a/src/commands/slash/Main/blacklist/list.ts b/src/commands/slash/Main/blacklist/list.ts index b967b99f..b78293ed 100644 --- a/src/commands/slash/Main/blacklist/list.ts +++ b/src/commands/slash/Main/blacklist/list.ts @@ -7,22 +7,23 @@ import { colors } from '../../../../utils/Constants.js'; export default class ListBlacklists extends BlacklistCommand { async execute(interaction: ChatInputCommandInteraction) { + await interaction.deferReply(); + const hub = interaction.options.getString('hub', true); - const hubInDb = await db.hubs.findFirst({ where: { - name: hub, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, + const hubInDb = await db.hubs.findFirst({ + where: { + name: hub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, }); if (!hubInDb) { - return await interaction.reply({ - content: 'Unknown hub. Make sure you are the owner or a moderator of the hub.', - ephemeral: true, - }); + await interaction.editReply('Unknown hub. Make sure you are the owner or a moderator of the hub.'); + return; } const serverOpt = interaction.options.getString('type'); @@ -39,65 +40,86 @@ export default class ListBlacklists extends BlacklistCommand { // repeat until you reach the end if (serverOpt == 'server') { - const result = await db.blacklistedServers.findMany({ where: { hubs: { some: { hubId: hubInDb.id } } } }); + const result = await db.blacklistedServers.findMany({ + where: { hubs: { some: { hubId: hubInDb.id } } }, + }); - result.forEach((data, index) => { + for (let i = 0; i < result.length; i++) { + const data = result[i]; const hubData = data.hubs.find(({ hubId }) => hubId === hubInDb.id); + const moderator = hubData?.moderatorId + ? await interaction.client.users.fetch(hubData?.moderatorId).catch(() => null) + : null; + fields.push({ name: data.serverName, value: stripIndents` **ServerId:** ${data.serverId} + **Moderator:** ${moderator ? `@${moderator.username} (${hubData?.moderatorId})` : 'Unknown'} **Reason:** ${hubData?.reason} - **Expires:** ${!hubData?.expires ? 'Never.' : ``} + **Expires:** ${ + !hubData?.expires ? 'Never.' : `` +} `, }); counter++; - if (counter >= LIMIT || index === result.length - 1) { - embeds.push(new EmbedBuilder() - .setFields(fields) - .setColor('#0099ff') - .setAuthor({ - name: 'Blacklisted Servers:', - iconURL: interaction.client.user?.avatarURL()?.toString(), - })); + if (counter >= LIMIT || i === result.length - 1) { + embeds.push( + new EmbedBuilder() + .setFields(fields) + .setColor('#0099ff') + .setAuthor({ + name: 'Blacklisted Servers:', + iconURL: interaction.client.user?.avatarURL()?.toString(), + }), + ); counter = 0; fields = []; } - }); + } } else if (serverOpt == 'user') { - const result = await db.blacklistedUsers.findMany({ where: { hubs: { some: { hubId: hubInDb.id } } } }); + const result = await db.blacklistedUsers.findMany({ + where: { hubs: { some: { hubId: hubInDb.id } } }, + }); - result.forEach((data, index) => { + for (let i = 0; i < result.length; i++) { + const data = result[i]; const hubData = data.hubs.find(({ hubId }) => hubId === hubInDb.id); + const moderator = hubData?.moderatorId + ? await interaction.client.users.fetch(hubData?.moderatorId).catch(() => null) + : null; fields.push({ name: data.username, value: stripIndents` **UserID:** ${data.userId} + **Moderator:** ${moderator ? `@${moderator.username} (${hubData?.moderatorId})` : 'Unknown'} **Reason:** ${hubData?.reason} **Expires:** ${!hubData?.expires ? 'Never.' : ``} `, }); counter++; - if (counter >= LIMIT || index === result.length - 1) { - embeds.push(new EmbedBuilder() - .setFields(fields) - .setColor(colors.interchatBlue) - .setAuthor({ - name: 'Blacklisted Users:', - iconURL: interaction.client.user?.avatarURL()?.toString(), - })); + if (counter >= LIMIT || i === result.length - 1) { + embeds.push( + new EmbedBuilder() + .setFields(fields) + .setColor(colors.interchatBlue) + .setAuthor({ + name: 'Blacklisted Users:', + iconURL: interaction.client.user?.avatarURL()?.toString(), + }), + ); counter = 0; fields = []; } - }); + } } paginate(interaction, embeds); } -} \ No newline at end of file +} diff --git a/src/commands/slash/Main/blacklist/server.ts b/src/commands/slash/Main/blacklist/server.ts index 54d067ec..91c92392 100644 --- a/src/commands/slash/Main/blacklist/server.ts +++ b/src/commands/slash/Main/blacklist/server.ts @@ -48,7 +48,13 @@ export default class UserBlacklist extends BlacklistCommand { if (!server) return await interaction.followUp('You have inputted an invalid server ID.'); try { - await blacklistManager.addServerBlacklist(server.id, hubInDb.id, reason, expires); + await blacklistManager.addServerBlacklist( + server.id, + hubInDb.id, + reason, + interaction.user.id, + expires, + ); } catch (err) { interaction.client.logger.error(err); diff --git a/src/commands/slash/Main/blacklist/user.ts b/src/commands/slash/Main/blacklist/user.ts index f3eb18a4..fa9dd04e 100644 --- a/src/commands/slash/Main/blacklist/user.ts +++ b/src/commands/slash/Main/blacklist/user.ts @@ -52,7 +52,7 @@ export default class Server extends BlacklistCommand { } const expires = duration ? new Date(Date.now() + duration) : undefined; - await blacklistManager.addUserBlacklist(hubInDb.id, user.id, String(reason), expires); + await blacklistManager.addUserBlacklist(hubInDb.id, user.id, String(reason), interaction.user.id, expires); if (expires) blacklistManager.scheduleRemoval('user', user.id, hubInDb.id, expires); blacklistManager.notifyBlacklist('user', user.id, hubInDb.id, expires, String(reason)); diff --git a/src/commands/slash/Main/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts index 102defff..c14c2885 100644 --- a/src/commands/slash/Main/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -363,20 +363,35 @@ export default class Browse extends Hub { // utils static createHubListingsEmbed(hub: hubs, connections?: number, lastMessage?: Date) { + const rating = calculateAverageRating(hub.rating.map((hr) => hr.rating)); + const stars = + rating < 5 + ? emojis.star.repeat(rating) + emojis.star_empty.repeat(5 - rating) + : emojis.star.repeat(5); + + const lastMessageTimestamp = lastMessage?.getTime() ?? 0; + const lastMessageStr = lastMessageTimestamp + ? `` + : '-'; + return new EmbedBuilder() - .setDescription( - stripIndents` - ### ${hub.name} - ${hub.description} - - **Rating:** ${hub.rating?.length > 0 - ? '⭐'.repeat(calculateAverageRating(hub.rating.map((hr) => hr.rating))) - : '-' -} - **Connections:** ${connections ?? 'Unknown.'} - **Created At:** - **Last Message:** ${lastMessage ? `` : '-'} - `, + .setTitle(hub.name) + .setDescription(hub.description) + .addFields( + { + name: 'Information', + value: stripIndents` + ${emojis.connect_icon} **Connections:** ${connections ?? 'Unknown.'} + ${emojis.clock_icon} **Created At:** + ${emojis.chat_icon} **Last Message:** ${lastMessageStr} + `, + inline: true, + }, + { + name: 'Rating', + value: `${stars}${rating ? `\n*rated by ${hub.rating.length} users*` : ''}`, + inline: true, + }, ) .setColor('Random') .setThumbnail(hub.iconUrl) diff --git a/src/index.ts b/src/index.ts index 10bd3931..ae0c7158 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,24 +72,26 @@ const processAndManageBlacklists = async (blacklists: (blacklistedServers | blac manager.on('clusterCreate', async (cluster) => { // if it is the last cluster and code is in production if (cluster.id === manager.totalClusters - 1 && !isDevBuild) { - // give time for shards to connect + const scheduler = new Scheduler(); + // remove expired blacklists or set new timers for them + const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; + processAndManageBlacklists(await db.blacklistedServers.findMany(query), scheduler); + processAndManageBlacklists(await db.blacklistedUsers.findMany(query), scheduler); + + + // give time for shards to connect for these tasks await wait(10_000); - // perform tasks on start up + // perform start up tasks syncBotlistStats(); deleteOldMessages(); deleteExpiredInvites(); - const scheduler = new Scheduler(); - // update top.gg stats every 10 minutes - scheduler.addRecurringTask('syncBotlistStats', 60 * 10_000, syncBotlistStats); // every 10 minutes - scheduler.addRecurringTask('deleteExpiredInvites', 60 * 60 * 1_000, deleteExpiredInvites); // every hour + scheduler.addRecurringTask('syncBotlistStats', 60 * 10_000, syncBotlistStats); + // delete expired invites every hour + scheduler.addRecurringTask('deleteExpiredInvites', 60 * 60 * 1_000, deleteExpiredInvites); + // delete old network messages every 12 hours scheduler.addRecurringTask('deleteOldMessages', 60 * 60 * 12_000, deleteOldMessages); // every 12 hours - - // remove expired blacklists or set new timers for them - const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; - processAndManageBlacklists(await db.blacklistedServers.findMany(query), scheduler); - processAndManageBlacklists(await db.blacklistedUsers.findMany(query), scheduler); } }); diff --git a/src/managers/BlacklistManager.ts b/src/managers/BlacklistManager.ts index 77bdf4b4..d313af59 100644 --- a/src/managers/BlacklistManager.ts +++ b/src/managers/BlacklistManager.ts @@ -2,7 +2,7 @@ import db from '../utils/Db.js'; import Scheduler from '../services/SchedulerService.js'; import SuperClient from '../SuperClient.js'; import { blacklistedServers, blacklistedUsers } from '@prisma/client'; -import { EmbedBuilder } from 'discord.js'; +import { EmbedBuilder, Snowflake } from 'discord.js'; import { emojis, colors } from '../utils/Constants.js'; import { captureException } from '@sentry/node'; import Logger from '../utils/Logger.js'; @@ -20,7 +20,7 @@ export default class BlacklistManager { * @param hubId The hub ID to remove the blacklist from. * @param userOrServerId The user or server ID to remove from the blacklist. * @returns The updated blacklist. - */ + */ async removeBlacklist( type: 'server', hubId: string, @@ -149,7 +149,7 @@ export default class BlacklistManager { * Fetch a user blacklist from the database. * @param hubId The hub ID to fetch the blacklist from. * @param userId The ID of the blacklisted user. - */ + */ static async fetchUserBlacklist(hubId: string, userId: string) { const userBlacklisted = await db.blacklistedUsers.findFirst({ where: { userId, hubs: { some: { hubId } } }, @@ -161,7 +161,7 @@ export default class BlacklistManager { * Fetch a server blacklist from the database. * @param hubId The hub ID to fetch the blacklist from. * @param serverId The ID of the blacklisted serverId. - */ + */ static async fetchServerBlacklist(hubId: string, serverId: string) { const userBlacklisted = await db.blacklistedServers.findFirst({ where: { serverId, hubs: { some: { hubId } } }, @@ -177,7 +177,13 @@ export default class BlacklistManager { * @param expires The date or milliseconds after which the blacklist will expire. * @returns The created blacklist. */ - async addUserBlacklist(hubId: string, userId: string, reason: string, expires?: Date | number) { + async addUserBlacklist( + hubId: string, + userId: Snowflake, + reason: string, + moderatorId: Snowflake, + expires?: Date | number, + ) { const client = SuperClient.getInstance(); const user = await client.users.fetch(userId); if (typeof expires === 'number') expires = new Date(Date.now() + expires); @@ -185,7 +191,7 @@ export default class BlacklistManager { const dbUser = await db.blacklistedUsers.findFirst({ where: { userId: user.id } }); const hubs = dbUser?.hubs.filter((i) => i.hubId !== hubId) || []; - hubs?.push({ expires: expires ?? null, reason, hubId }); + hubs?.push({ expires: expires ?? null, reason, hubId, moderatorId }); const updatedUser = await db.blacklistedUsers.upsert({ where: { @@ -213,7 +219,13 @@ export default class BlacklistManager { * @param expires The date after which the blacklist will expire. * @returns The created blacklist. */ - async addServerBlacklist(serverId: string, hubId: string, reason: string, expires?: Date) { + async addServerBlacklist( + serverId: Snowflake, + hubId: string, + reason: string, + moderatorId: Snowflake, + expires?: Date, + ) { const client = SuperClient.getInstance(); const guild = await client.fetchGuild(serverId); if (!guild) return; @@ -224,12 +236,12 @@ export default class BlacklistManager { }, update: { serverName: guild.name, - hubs: { push: { hubId, expires, reason } }, + hubs: { push: { hubId, expires, reason, moderatorId } }, }, create: { serverId: guild.id, serverName: guild.name, - hubs: [{ hubId, expires, reason }], + hubs: [{ hubId, expires, reason, moderatorId }], }, }); diff --git a/src/managers/NetworkManager.ts b/src/managers/NetworkManager.ts index a91facbe..8e4af093 100644 --- a/src/managers/NetworkManager.ts +++ b/src/managers/NetworkManager.ts @@ -116,7 +116,7 @@ export default class NetworkManager extends Factory { const { embed, censoredEmbed } = this.buildNetworkEmbed(message, { attachmentURL, referredContent, - embedCol: isNetworkMessage.embedColor as `#${string}` ?? undefined, + embedCol: (isNetworkMessage.embedColor as `#${string}`) ?? undefined, useNicknames: settings.has('UseNicknames'), }); @@ -181,9 +181,10 @@ export default class NetworkManager extends Factory { messageFormat = { embeds: replyEmbed ? [replyEmbed] : undefined, components: jumpButton ? [jumpButton] : undefined, - content: (connection.profFilter ? message.censoredContent : message.content) + - // append the attachment url if there is one - `${attachmentURL ? `\n${attachmentURL}` : ''}`, + content: + (connection.profFilter ? message.censoredContent : message.content) + + // append the attachment url if there is one + `${attachment ? `\n${attachmentURL}` : ''}`, username: message.author.username, avatarURL: message.author.displayAvatarURL(), threadId: connection.parentId ? connection.channelId : undefined, @@ -209,7 +210,12 @@ export default class NetworkManager extends Factory { if (!attachment) message.delete().catch(() => null); // store the message in the db - await this.storeMessageData(message, await Promise.all(sendResult), isNetworkMessage.hubId, referenceInDb); + await this.storeMessageData( + message, + await Promise.all(sendResult), + isNetworkMessage.hubId, + referenceInDb, + ); } /** @@ -262,6 +268,7 @@ export default class NetworkManager extends Factory { hubId, message.author.id, 'Auto-blacklisted for spamming.', + message.client.user.id, 60 * 5000, ); blacklistManager.scheduleRemoval('user', message.author.id, hubId, 60 * 5000); diff --git a/src/utils/JSON/emojis.json b/src/utils/JSON/emojis.json index 4b8b029c..2eaf4b43 100644 --- a/src/utils/JSON/emojis.json +++ b/src/utils/JSON/emojis.json @@ -65,7 +65,15 @@ "staff": "<:staff:983962462380511282>", "botdev": "<:botdev2:983962441329307659>", "link": "<:link:1032318209879179295>", - "timeout": "<:timeout:1088474245908152461>" + "timeout": "<:timeout:1088474245908152461>", + + "star": "<:ic_star:1166196600666857472>", + "star_empty": "<:ic_star_tr:1166196596887793704>", + "chat_icon": "<:ic_chat:1165879694403579904>", + "clock_icon": "<:ic_clock:1165849366578339840>", + "connect_icon": "<:ic_connect:1165877460190101544>", + "timeout_icon": "<:ic_timeout:1165843806738980925>", + "webhook_icon": "<:ic_webhook:1165851459066277989>" }, "mascot": { From 1cf5aa92c7381b06e8b8f0904e92c9f262dcec6a Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:47:22 +0530 Subject: [PATCH 09/13] fix more bugs and remove command list from help cmd --- src/commands/context-menu/blacklist.ts | 8 +++--- src/commands/context-menu/editMsg.ts | 2 +- src/commands/slash/Information/help.ts | 15 ++-------- src/commands/slash/Main/hub/browse.ts | 39 ++++++++++++++++++++++++++ src/commands/slash/Main/hub/join.ts | 2 +- src/commands/slash/Main/hub/manage.ts | 1 + src/managers/NetworkManager.ts | 10 ++++--- src/utils/Pagination.ts | 4 ++- 8 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts index b0b2b1b8..fd015ceb 100644 --- a/src/commands/context-menu/blacklist.ts +++ b/src/commands/context-menu/blacklist.ts @@ -224,10 +224,6 @@ export default class Blacklist extends BaseCommand { reason, ); - await db.connectedList.deleteMany({ - where: { serverId: messageInDb.serverId, hubId: messageInDb.hubId }, - }); - if (expires) { blacklistManager.scheduleRemoval( 'server', @@ -237,6 +233,10 @@ export default class Blacklist extends BaseCommand { ); } + await db.connectedList.deleteMany({ + where: { serverId: messageInDb.serverId, hubId: messageInDb.hubId }, + }); + await interaction.editReply({ embeds: [successEmbed], components: [] }); } } diff --git a/src/commands/context-menu/editMsg.ts b/src/commands/context-menu/editMsg.ts index c7846f39..ac099320 100644 --- a/src/commands/context-menu/editMsg.ts +++ b/src/commands/context-menu/editMsg.ts @@ -29,7 +29,7 @@ export default class DeleteMessage extends BaseCommand { async execute(interaction: MessageContextMenuCommandInteraction) { const target = interaction.targetMessage; - if (!(await hasVoted(interaction.user.id)) && !checkIfStaff(interaction.user.id)) { + if (!checkIfStaff(interaction.user.id) && !(await hasVoted(interaction.user.id))) { await interaction.reply({ content: `${emojis.no} You must [vote]() to use this command.`, ephemeral: true, diff --git a/src/commands/slash/Information/help.ts b/src/commands/slash/Information/help.ts index 5cb7cb54..381238e9 100644 --- a/src/commands/slash/Information/help.ts +++ b/src/commands/slash/Information/help.ts @@ -22,6 +22,7 @@ export default class Help extends BaseCommand { }; async execute(interaction: ChatInputCommandInteraction) { + // TODO ${emojis.slashCommand} [**All Commands**](https://discord-interchat.github.io/docs/category/commands) const embed = new EmbedBuilder() .setColor(colors.interchatBlue) .setThumbnail(interaction.client.user.avatarURL()) @@ -30,7 +31,6 @@ export default class Help extends BaseCommand { ## InterChat Help InterChat is a powerful discord bot that enables effortless cross-server chatting! Get started by looking at the categories below. ### Categories: - - ${emojis.slashCommand} [**All Commands**](https://discord-interchat.github.io/docs/category/commands) - 👥 [**InterChat Hubs**](https://discord-interchat.github.io/docs/hub/joining) - ⚙️ [**Setting up InterChat**](https://discord-interchat.github.io/docs/setup) - 💬 [**Messaging & Network**](https://discord-interchat.github.io/docs/messaging) @@ -66,12 +66,6 @@ export default class Help extends BaseCommand { emoji: '💬', description: 'How to send, edit, delete and react to network messages!', }, - { - label: 'Commands', - value: 'commands', - emoji: emojis.slashCommand, - description: 'View all of InterChat\'s commands.', - }, { label: 'The Team', value: 'credits', @@ -98,7 +92,7 @@ export default class Help extends BaseCommand { .setURL(URLs.SUPPORT_INVITE), new ButtonBuilder() .setStyle(ButtonStyle.Link) - .setLabel('Vote me!') + .setLabel('Vote!') .setURL('https://top.gg/bot/769921109209907241/vote'), ); @@ -183,10 +177,7 @@ export default class Help extends BaseCommand { await interaction.update({ embeds: [messagingEmbed] }); break; } - case 'commands': { - await interaction.reply('no'); - break; - } + case 'credits': { await interaction.deferUpdate(); diff --git a/src/commands/slash/Main/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts index c14c2885..9bb20bab 100644 --- a/src/commands/slash/Main/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -24,6 +24,7 @@ import { showOnboarding } from '../../../../scripts/network/onboarding.js'; import { CustomID } from '../../../../structures/CustomID.js'; import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { stripIndents } from 'common-tags'; +import BlacklistManager from '../../../../managers/BlacklistManager.js'; export default class Browse extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { @@ -239,6 +240,28 @@ export default class Browse extends Hub { if (!interaction.inCachedGuild()) return; + const userBlacklisted = await BlacklistManager.fetchUserBlacklist( + hubDetails.id, + interaction.user.id, + ); + if (userBlacklisted) { + return await interaction.reply({ + content: `You have been blacklisted from joining **${hubDetails.name}**.`, + ephemeral: true, + }); + } + + const serverBlacklisted = await BlacklistManager.fetchServerBlacklist( + hubDetails.id, + interaction.guild.id, + ); + if (serverBlacklisted) { + return await interaction.reply({ + content: `This server has been blacklisted from joining **${hubDetails.name}**.`, + ephemeral: true, + }); + } + const channel = interaction.isChannelSelectMenu() ? interaction.guild?.channels.cache.get(interaction.values[0]) : interaction.channel; @@ -319,6 +342,22 @@ export default class Browse extends Hub { embeds: [], components: [], }); + + const totalConnections = await db.connectedList.count({ + where: { hubId: hubDetails.id, connected: true }, + }); + + // announce a new server has joined the hub + networkManager.sendToNetwork(hubDetails.id, { + content: stripIndents` + A new server has joined us! ${emojis.clipart} + + **Server Name:** __${interaction.guild.name}__ + **Member Count:** __${interaction.guild.memberCount}__ + + We now have **${totalConnections}** servers in the hub! + `, + }); } } diff --git a/src/commands/slash/Main/hub/join.ts b/src/commands/slash/Main/hub/join.ts index f0c1a30b..f98ad66d 100644 --- a/src/commands/slash/Main/hub/join.ts +++ b/src/commands/slash/Main/hub/join.ts @@ -72,7 +72,7 @@ export default class JoinSubCommand extends Hub { } const userBlacklisted = await BlacklistManager.fetchUserBlacklist(hub.id, interaction.user.id); - const serverBlacklisted = await BlacklistManager.fetchUserBlacklist( + const serverBlacklisted = await BlacklistManager.fetchServerBlacklist( hub.id, interaction.guildId, ); diff --git a/src/commands/slash/Main/hub/manage.ts b/src/commands/slash/Main/hub/manage.ts index 209fbc31..6e3d138a 100644 --- a/src/commands/slash/Main/hub/manage.ts +++ b/src/commands/slash/Main/hub/manage.ts @@ -249,6 +249,7 @@ export default class Manage extends Hub { }); await interaction.reply({ content: 'Successfully removed banner!', ephemeral: true }); + break; } const bannerUrl = await checkAndFetchImgurUrl(newBanner); diff --git a/src/managers/NetworkManager.ts b/src/managers/NetworkManager.ts index 8e4af093..96d35eaf 100644 --- a/src/managers/NetworkManager.ts +++ b/src/managers/NetworkManager.ts @@ -380,6 +380,8 @@ export default class NetworkManager extends Factory { useNicknames?: boolean; }, ): { embed: EmbedBuilder; censoredEmbed: EmbedBuilder } { + const formattedContent = opts?.referredContent?.replaceAll('\n', '\n> '); + const embed = new EmbedBuilder() .setAuthor({ name: opts?.useNicknames @@ -389,11 +391,11 @@ export default class NetworkManager extends Factory { }) .setDescription(message.content || null) .addFields( - opts?.referredContent + formattedContent ? [ { name: 'Replying To:', - value: `> ${opts.referredContent.replaceAll('\n', '\n> ')}` ?? 'Unknown.', + value: `> ${formattedContent}` ?? 'Unknown.', }, ] : [], @@ -408,11 +410,11 @@ export default class NetworkManager extends Factory { const censoredEmbed = EmbedBuilder.from(embed) .setDescription(message.censoredContent || null) .setFields( - opts?.referredContent + formattedContent ? [ { name: 'Replying To:', - value: `> ${censor(opts.referredContent).replaceAll('\n', '\n> ')}` ?? 'Unknown.', + value: `> ${censor(formattedContent)}` ?? 'Unknown.', }, ] : [], diff --git a/src/utils/Pagination.ts b/src/utils/Pagination.ts index 50952c26..4892bcb4 100644 --- a/src/utils/Pagination.ts +++ b/src/utils/Pagination.ts @@ -31,7 +31,9 @@ export async function paginate( options?: PaginatorOptions, ) { if (pages.length < 1) { - interaction.reply({ content: `${emojis.tick} No more pages to display!`, ephemeral: true }); + interaction.replied || interaction.deferred + ? await interaction.followUp({ content: `${emojis.tick} No pages to display!`, ephemeral: true }) + : await interaction.reply({ content: `${emojis.tick} No pages to display!`, ephemeral: true }); return; } From be856aec325ca4fe1c6c346e837e40db0cfeaeb1 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sun, 5 Nov 2023 16:31:27 +0530 Subject: [PATCH 10/13] feat: network logs --- prisma/schema.prisma | 8 + src/commands/context-menu/blacklist.ts | 14 +- src/commands/slash/Main/blacklist/server.ts | 11 +- src/commands/slash/Main/blacklist/user.ts | 60 +++--- src/commands/slash/Main/hub/index.ts | 46 ++++- src/commands/slash/Main/hub/logging.ts | 59 ++++++ src/commands/slash/Main/hub/manage.ts | 10 + src/commands/slash/Main/hub/settings.ts | 72 +------ src/index.ts | 13 +- src/managers/BlacklistManager.ts | 11 +- src/managers/CommandManager.ts | 8 +- src/managers/NetworkManager.ts | 12 +- src/scripts/hub/settings.ts | 56 ++++++ src/structures/NetworkLogger.ts | 196 ++++++++++++++++++++ src/utils/JSON/emojis.json | 1 - src/utils/Profanity.ts | 47 +++-- src/utils/Utils.ts | 6 + tests/ImgurLinks.test.ts | 4 +- 18 files changed, 501 insertions(+), 133 deletions(-) create mode 100644 src/commands/slash/Main/hub/logging.ts create mode 100644 src/scripts/hub/settings.ts create mode 100644 src/structures/NetworkLogger.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2a9a22a8..1ae0bc16 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -48,6 +48,13 @@ type HubModerator { // spamFilter Boolean @default(true) // } +type HubLogChannels { + reports String? + modLogs String? + joinLeave String? + profanity String? +} + type hubBlacklist { reason String expires DateTime? @@ -102,6 +109,7 @@ model hubs { messages messageData[] moderators HubModerator[] connections connectedList[] + logChannels HubLogChannels? // approved Boolean @default(false) // official Boolean @default(false) } diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts index fd015ceb..83d1538e 100644 --- a/src/commands/context-menu/blacklist.ts +++ b/src/commands/context-menu/blacklist.ts @@ -19,6 +19,7 @@ import { CustomID } from '../../structures/CustomID.js'; import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; import { errorEmbed } from '../../utils/Utils.js'; import parse from 'parse-duration'; +import NetworkLogger from '../../structures/NetworkLogger.js'; export default class Blacklist extends BaseCommand { data: RESTPostAPIApplicationCommandsJSONBody = { @@ -196,6 +197,9 @@ export default class Blacklist extends BaseCommand { blacklistManager .notifyBlacklist('user', messageInDb.authorId, messageInDb.hubId, expires, reason) .catch(() => null); + + const networkLogger = new NetworkLogger(messageInDb.hubId); + await networkLogger.logBlacklist(user, interaction.user, reason, expires); } await interaction.editReply({ embeds: [successEmbed], components: [] }); @@ -203,9 +207,10 @@ export default class Blacklist extends BaseCommand { // server blacklist else { + const server = interaction.client.guilds.cache.get(messageInDb.serverId); + successEmbed.setDescription( - `${emojis.tick} **${interaction.client.guilds.cache.get(messageInDb.serverId) - ?.name}** has been successfully blacklisted!`, + `${emojis.tick} **${server?.name}** has been successfully blacklisted!`, ); await blacklistManager.addServerBlacklist( messageInDb.serverId, @@ -237,6 +242,11 @@ export default class Blacklist extends BaseCommand { where: { serverId: messageInDb.serverId, hubId: messageInDb.hubId }, }); + if (server) { + const networkLogger = new NetworkLogger(messageInDb.hubId); + await networkLogger.logBlacklist(server, interaction.user, reason, expires).catch(() => null); + } + await interaction.editReply({ embeds: [successEmbed], components: [] }); } } diff --git a/src/commands/slash/Main/blacklist/server.ts b/src/commands/slash/Main/blacklist/server.ts index 91c92392..ff1608e2 100644 --- a/src/commands/slash/Main/blacklist/server.ts +++ b/src/commands/slash/Main/blacklist/server.ts @@ -5,6 +5,7 @@ import db from '../../../../utils/Db.js'; import BlacklistCommand from './index.js'; import BlacklistManager from '../../../../managers/BlacklistManager.js'; import parse from 'parse-duration'; +import NetworkLogger from '../../../../structures/NetworkLogger.js'; export default class UserBlacklist extends BlacklistCommand { async execute(interaction: ChatInputCommandInteraction) { @@ -34,6 +35,8 @@ export default class UserBlacklist extends BlacklistCommand { const subCommandGroup = interaction.options.getSubcommandGroup(); const serverOpt = interaction.options.getString('server', true); + const networkLogger = new NetworkLogger(hubInDb.id); + if (subCommandGroup == 'add') { const reason = interaction.options.getString('reason', true); const duration = parse(`${interaction.options.getString('duration')}`); @@ -75,7 +78,7 @@ export default class UserBlacklist extends BlacklistCommand { .addFields( { name: 'Reason', - value: reason ? reason : 'No reason provided.', + value: reason ?? 'No reason provided.', inline: true, }, { @@ -92,6 +95,9 @@ export default class UserBlacklist extends BlacklistCommand { // delete all connections from db so they can't reconnect to the hub await db.connectedList.deleteMany({ where: { serverId: server.id, hubId: hubInDb.id } }); + + // send log to hub's log channel + await networkLogger.logBlacklist(server, interaction.user, reason, expires); } else if (subCommandGroup == 'remove') { const result = await blacklistManager.removeBlacklist('server', hubInDb.id, serverOpt); @@ -101,6 +107,9 @@ export default class UserBlacklist extends BlacklistCommand { await interaction.followUp( `The server **${result.serverName}** has been removed from the blacklist.`, ); + + // send log to hub's log channel + await networkLogger.logUnblacklist('user', serverOpt, interaction.user); } } } diff --git a/src/commands/slash/Main/blacklist/user.ts b/src/commands/slash/Main/blacklist/user.ts index fa9dd04e..18c5df00 100644 --- a/src/commands/slash/Main/blacklist/user.ts +++ b/src/commands/slash/Main/blacklist/user.ts @@ -4,6 +4,7 @@ import BlacklistCommand from './index.js'; import BlacklistManager from '../../../../managers/BlacklistManager.js'; import parse from 'parse-duration'; import { emojis } from '../../../../utils/Constants.js'; +import NetworkLogger from '../../../../structures/NetworkLogger.js'; export default class Server extends BlacklistCommand { async execute(interaction: ChatInputCommandInteraction) { @@ -11,13 +12,14 @@ export default class Server extends BlacklistCommand { const hub = interaction.options.getString('hub', true); - const hubInDb = await db.hubs.findFirst({ where: { - name: hub, - OR: [ - { ownerId: interaction.user.id }, - { moderators: { some: { userId: interaction.user.id } } }, - ], - }, + const hubInDb = await db.hubs.findFirst({ + where: { + name: hub, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id } } }, + ], + }, }); if (!hubInDb) { @@ -27,9 +29,11 @@ export default class Server extends BlacklistCommand { }); } + const networkLogger = new NetworkLogger(hubInDb.id); + const subcommandGroup = interaction.options.getSubcommandGroup(); const userId = interaction.options.getString('user', true); - const reason = interaction.options.getString('reason'); + const reason = interaction.options.getString('reason') ?? 'No reason provided.'; const duration = parse(`${interaction.options.getString('duration')}`); const blacklistManager = interaction.client.getBlacklistManager(); @@ -38,12 +42,13 @@ export default class Server extends BlacklistCommand { // get ID if user inputted a @ mention const userOpt = userId.replaceAll(/<@|!|>/g, ''); // find user through username if they are cached or fetch them using ID - const user = interaction.client.users.cache.find((u) => u.username === userOpt) ?? - await interaction.client.users.fetch(userOpt).catch(() => null); + const user = + interaction.client.users.cache.find((u) => u.username === userOpt) ?? + (await interaction.client.users.fetch(userOpt).catch(() => null)); if (!user) return interaction.followUp('Could not find user. Use an ID instead.'); // if (user.id === interaction.user.id) return interaction.followUp('You cannot blacklist yourself.'); - if (user.id === interaction.client.user?.id) return interaction.followUp('You cannot blacklist the bot wtf.'); + if (user.id === interaction.client.user?.id) {return interaction.followUp('You cannot blacklist the bot wtf.');} const userInBlacklist = await BlacklistManager.fetchUserBlacklist(hubInDb.id, userOpt); if (userInBlacklist) { @@ -52,9 +57,15 @@ export default class Server extends BlacklistCommand { } const expires = duration ? new Date(Date.now() + duration) : undefined; - await blacklistManager.addUserBlacklist(hubInDb.id, user.id, String(reason), interaction.user.id, expires); + await blacklistManager.addUserBlacklist( + hubInDb.id, + user.id, + reason, + interaction.user.id, + expires, + ); if (expires) blacklistManager.scheduleRemoval('user', user.id, hubInDb.id, expires); - blacklistManager.notifyBlacklist('user', user.id, hubInDb.id, expires, String(reason)); + blacklistManager.notifyBlacklist('user', user.id, hubInDb.id, expires, reason); const successEmbed = new EmbedBuilder() .setDescription(`${emojis.tick} **${user.username}** has been successfully blacklisted!`) @@ -62,7 +73,7 @@ export default class Server extends BlacklistCommand { .addFields( { name: 'Reason', - value: reason ? reason : 'No reason provided.', + value: reason, inline: true, }, { @@ -73,26 +84,23 @@ export default class Server extends BlacklistCommand { ); await interaction.followUp({ embeds: [successEmbed] }); - } + // send log to hub's log channel + await networkLogger.logBlacklist(user, interaction.user, reason, expires); + } else if (subcommandGroup == 'remove') { + // remove the blacklist const result = await blacklistManager.removeBlacklist('user', hubInDb.id, userId); if (!result) return interaction.followUp('The inputted user is not blacklisted.'); - const user = await interaction.client.users.fetch(userId).catch(() => null); await interaction.followUp(`**${user?.username}** has been removed from the blacklist.`); - // TODO: Logging - // if (user) { - // modActions(interaction.user, { - // user, - // action: 'unblacklistUser', - // blacklistedFor: blacklistedUser.hubs.find(({ hubId }) => hubId === hubInDb.id)?.reason, - // hubId: hubInDb.id, - // }); - // } + if (user) { + // send log to hub's log channel + await networkLogger.logUnblacklist('user', user.id, interaction.user, { reason }); + } } } -} \ No newline at end of file +} diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts index 1828913e..c12626a3 100644 --- a/src/commands/slash/Main/hub/index.ts +++ b/src/commands/slash/Main/hub/index.ts @@ -270,6 +270,37 @@ export default class Hub extends BaseCommand { name: 'joined', description: '📜 List all hubs you have joined from this server.', }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'logging', + description: '📁 Enable/disable logging for your hub.', + options: [ + hubOption, + { + type: ApplicationCommandOptionType.String, + name: 'log_type', + description: 'The type of logs you want to toggle.', + choices: [ + { name: 'reports', value: 'reports' }, + { name: 'profanity', value: 'profanity' }, + { name: 'modLogs', value: 'modLogs' }, + { name: 'memberLogs', value: 'memberLogs' }, + ], + required: true, + }, + { + type: ApplicationCommandOptionType.Channel, + name: 'channel', + description: 'The channel you want to send this type of logs to.', + required: true, + channel_types: [ + ChannelType.GuildText, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ], + }, + ], + }, ], }; @@ -284,7 +315,8 @@ export default class Hub extends BaseCommand { } async autocomplete(interaction: AutocompleteInteraction): Promise { - const modCmds = ['manage', 'settings', 'connections', 'invite', 'moderator']; + const managerCmds = ['manage', 'settings', 'invite', 'moderator', 'logging']; + const modCmds = ['connections']; const subcommand = interaction.options.getSubcommand(); const subcommandGroup = interaction.options.getSubcommandGroup(); @@ -312,6 +344,18 @@ export default class Hub extends BaseCommand { take: 25, }); } + else if (managerCmds.includes(subcommandGroup || subcommand)) { + hubChoices = await db.hubs.findMany({ + where: { + name: { mode: 'insensitive', contains: focusedValue }, + OR: [ + { ownerId: interaction.user.id }, + { moderators: { some: { userId: interaction.user.id, position: 'manager' } } }, + ], + }, + take: 25, + }); + } else if (subcommand === 'leave') { const networks = await db.connectedList.findMany({ where: { serverId: interaction.guild?.id }, diff --git a/src/commands/slash/Main/hub/logging.ts b/src/commands/slash/Main/hub/logging.ts new file mode 100644 index 00000000..9227943f --- /dev/null +++ b/src/commands/slash/Main/hub/logging.ts @@ -0,0 +1,59 @@ +import { ChatInputCommandInteraction, CacheType, EmbedBuilder, ChannelType } from 'discord.js'; +import db from '../../../../utils/Db.js'; +import Hub from './index.js'; +import { colors, emojis } from '../../../../utils/Constants.js'; +import { errorEmbed } from '../../../../utils/Utils.js'; +import { Prisma } from '@prisma/client'; + +export default class Logging extends Hub { + async execute(interaction: ChatInputCommandInteraction) { + const hub = interaction.options.getString('hub', true); + const type = interaction.options.getString( + 'log_type', + true, + ) as keyof Prisma.HubLogChannelsCreateInput; + const channel = interaction.options.getChannel('channel', true, [ + ChannelType.GuildText, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ]); + + const hubInDb = await db.hubs.findFirst({ where: { name: hub } }); + + if (!hubInDb) { + return await interaction.reply({ + embeds: [errorEmbed(`${emojis.no} This hub does not exist.`)], + }); + } + if ( + hubInDb?.ownerId !== interaction.user.id && + hubInDb?.moderators.some( + (mod) => mod.userId === interaction.user.id && mod.position === 'manager', + ) + ) { + return await interaction.reply({ + embeds: [ + errorEmbed(`${emojis.no} You are not allowed to perform this action on this hub.`), + ], + }); + } + + await db.hubs.update({ + where: { id: hubInDb.id }, + data: { + logChannels: { + set: { [type]: channel.id }, + }, + }, + }); + + const embed = new EmbedBuilder() + .setTitle('Log Channel Set') + .setDescription( + `${emojis.yes} ${channel} will be used for sending \`${type}\` logs from now on.`, + ) + .setColor(colors.invisible); + + await interaction.reply({ embeds: [embed] }); + } +} diff --git a/src/commands/slash/Main/hub/manage.ts b/src/commands/slash/Main/hub/manage.ts index 6e3d138a..f5a72e29 100644 --- a/src/commands/slash/Main/hub/manage.ts +++ b/src/commands/slash/Main/hub/manage.ts @@ -337,6 +337,7 @@ export default class Manage extends Hub { const hubBlacklistedServers = await db.blacklistedServers.count({ where: { hubs: { some: { hubId: hub.id } } }, }); + return new EmbedBuilder() .setTitle(hub.name) .setColor('Random') @@ -367,6 +368,15 @@ export default class Manage extends Hub { `, inline: true, }, + { + name: 'Log Channels', + value: stripIndents` + - Profanity: ${hub?.logChannels?.profanity ? `<#${hub?.logChannels?.profanity}>` : emojis.no} + - Mod Logs: ${hub?.logChannels?.modLogs ? `<#${hub?.logChannels?.modLogs}>` : emojis.no} + - Reports: ${hub?.logChannels?.reports ? `<#${hub?.logChannels?.reports}>` : emojis.no} + `, + inline: true, + }, ); } } diff --git a/src/commands/slash/Main/hub/settings.ts b/src/commands/slash/Main/hub/settings.ts index bf17cba1..2bcccb19 100644 --- a/src/commands/slash/Main/hub/settings.ts +++ b/src/commands/slash/Main/hub/settings.ts @@ -1,20 +1,13 @@ -import { - ChatInputCommandInteraction, - CacheType, - ActionRowBuilder, - EmbedBuilder, - StringSelectMenuBuilder, - Snowflake, -} from 'discord.js'; import db from '../../../../utils/Db.js'; import Hub from './index.js'; -import { hubs } from '@prisma/client'; +import { ChatInputCommandInteraction, CacheType } from 'discord.js'; import { HubSettingsBitField, HubSettingsString } from '../../../../utils/BitFields.js'; -import { colors, emojis } from '../../../../utils/Constants.js'; +import { emojis } from '../../../../utils/Constants.js'; import { RegisterInteractionHandler } from '../../../../decorators/Interaction.js'; import { CustomID } from '../../../../structures/CustomID.js'; import { StringSelectMenuInteraction } from 'discord.js'; import { errorEmbed } from '../../../../utils/Utils.js'; +import { buildSettingsEmbed, buildSettingsMenu } from '../../../../scripts/hub/settings.js'; export default class Settings extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { @@ -39,8 +32,8 @@ export default class Settings extends Hub { } const hubSettings = new HubSettingsBitField(hub.settings); - const embed = Settings.buildSettingsEmbed(hub); - const selects = Settings.settingsMenu(hubSettings, hubName, interaction.user.id); + const embed = buildSettingsEmbed(hub); + const selects = buildSettingsMenu(hubSettings, hubName, interaction.user.id); await interaction.reply({ embeds: [embed], components: [selects] }); } @@ -89,63 +82,12 @@ export default class Settings extends Hub { data: { settings: hubSettings.toggle(selected).bitfield }, // toggle the setting }); - const embed = Settings.buildSettingsEmbed(hub); - const selects = Settings.settingsMenu(hubSettings, hub.name, interaction.user.id); + const embed = buildSettingsEmbed(hub); + const selects = buildSettingsMenu(hubSettings, hub.name, interaction.user.id); await interaction.update({ embeds: [embed], components: [selects], }); } - - static buildSettingsEmbed(hub: hubs) { - const settings = new HubSettingsBitField(hub.settings); - const settingDescriptions = { - Reactions: '**Reactions** - Allow users to react to messages.', - HideLinks: '**Hide Links** - Redact links sent by users.', - BlockInvites: '**Block Invites** - Prevent users from sending Discord invites.', - BlockNSFW: '**Block NSFW** - Detect and block NSFW images (static only).', - SpamFilter: '**Spam Filter** - Automatically blacklist spammers for 5 minutes.', - UseNicknames: '**Use Nicknames** - Use server nicknames as the network usernames.', - }; - - return new EmbedBuilder() - .setAuthor({ name: `${hub.name} Settings`, iconURL: hub.iconUrl }) - .setDescription( - Object.entries(settingDescriptions) - .map(([key, value]) => { - const flag = settings.has(key as HubSettingsString); - return `- ${flag ? emojis.enabled : emojis.disabled} ${value}`; - }) - .join('\n'), - ) - .setFooter({ text: 'Use the select menu below to toggle.' }) - .setColor(colors.interchatBlue) - .setTimestamp(); - } - - static settingsMenu(hubSettings: HubSettingsBitField, hubName: string, userId: Snowflake) { - return new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId( - new CustomID() - .setIdentifier('hub_settings', 'settings') - .addArgs(hubName) - .addArgs(userId) - .toString(), - ) - .setPlaceholder('Select an option') - .addOptions( - Object.keys(HubSettingsBitField.Flags).map((key) => { - const flag = hubSettings.has(key as HubSettingsString); - const emoji = flag ? emojis.no : emojis.yes; - return { - label: `${flag ? 'Disable' : 'Enable'} ${key}`, - value: key, - emoji, - }; - }), - ), - ); - } } diff --git a/src/index.ts b/src/index.ts index ae0c7158..7219e2c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,15 +70,14 @@ const processAndManageBlacklists = async (blacklists: (blacklistedServers | blac }; manager.on('clusterCreate', async (cluster) => { + const scheduler = new Scheduler(); + // remove expired blacklists or set new timers for them + const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; + processAndManageBlacklists(await db.blacklistedServers.findMany(query), scheduler); + processAndManageBlacklists(await db.blacklistedUsers.findMany(query), scheduler); + // if it is the last cluster and code is in production if (cluster.id === manager.totalClusters - 1 && !isDevBuild) { - const scheduler = new Scheduler(); - // remove expired blacklists or set new timers for them - const query = { where: { hubs: { some: { expires: { isSet: true } } } } }; - processAndManageBlacklists(await db.blacklistedServers.findMany(query), scheduler); - processAndManageBlacklists(await db.blacklistedUsers.findMany(query), scheduler); - - // give time for shards to connect for these tasks await wait(10_000); diff --git a/src/managers/BlacklistManager.ts b/src/managers/BlacklistManager.ts index d313af59..b6f0b5c3 100644 --- a/src/managers/BlacklistManager.ts +++ b/src/managers/BlacklistManager.ts @@ -26,7 +26,16 @@ export default class BlacklistManager { hubId: string, serverId: string, ): Promise; - async removeBlacklist(type: 'user', hubId: string, userId: string): Promise; + async removeBlacklist( + type: 'user', + hubId: string, + userId: string, + ): Promise; + async removeBlacklist( + type: 'server', + hubId: string, + userId: string, + ): Promise; async removeBlacklist(type: 'user' | 'server', hubId: string, userOrServerId: string) { this.scheduler.stopTask(`blacklist_${type}-${userOrServerId}`); const data = { diff --git a/src/managers/CommandManager.ts b/src/managers/CommandManager.ts index 4a45b5c2..5edbbd5d 100644 --- a/src/managers/CommandManager.ts +++ b/src/managers/CommandManager.ts @@ -116,6 +116,12 @@ export default class CommandManager extends Factory { * @param commandDir The directory to load command files from. */ static async loadCommandFiles(commandDir = join(__dirname, '..', 'commands')): Promise { + let importPrefix = ''; + if (process.platform === 'win32') { + importPrefix = 'file://'; + commandDir = commandDir.replace('\\C:\\', 'C:\\'); + } + const files = readdirSync(commandDir); for (const file of files) { @@ -129,7 +135,7 @@ export default class CommandManager extends Factory { // If the item is a .js file, read its contents else if (file.endsWith('.js') && file !== 'BaseCommand.js') { - const imported = await import(filePath); + const imported = await import(importPrefix + filePath); const command = new imported.default(); // if the command extends BaseCommand (ie. its not a subcommand), add it to the commands map diff --git a/src/managers/NetworkManager.ts b/src/managers/NetworkManager.ts index 96d35eaf..e232dd10 100644 --- a/src/managers/NetworkManager.ts +++ b/src/managers/NetworkManager.ts @@ -14,10 +14,11 @@ import Factory from '../Factory.js'; import db from '../utils/Db.js'; import { Prisma, connectedList, hubs, messageData } from '@prisma/client'; import { REGEX, emojis } from '../utils/Constants.js'; -import { censor } from '../utils/Profanity.js'; +import { check as checkProfanity, censor } from '../utils/Profanity.js'; import { stripIndents } from 'common-tags'; import { HubSettingsBitField } from '../utils/BitFields.js'; import { replaceLinks } from '../utils/Utils.js'; +import NetworkLogger from '../structures/NetworkLogger.js'; export interface NetworkMessage extends Message { censoredContent: string; @@ -311,6 +312,15 @@ export default class NetworkManager extends Factory { } } + const hasProfanity = checkProfanity(message.content); + if ((hasProfanity.profanity || hasProfanity.slurs) && message.guild) { + // send a log to the log channel set by the hub + new NetworkLogger(hubId).logProfanity(message.content, message.author, message.guild); + + // we dont want to send the message if it contains slurs + if (hasProfanity.slurs) return false; + } + return true; } diff --git a/src/scripts/hub/settings.ts b/src/scripts/hub/settings.ts new file mode 100644 index 00000000..aa227f3a --- /dev/null +++ b/src/scripts/hub/settings.ts @@ -0,0 +1,56 @@ +import { ActionRowBuilder, EmbedBuilder, Snowflake, StringSelectMenuBuilder } from 'discord.js'; +import { HubSettingsBitField, HubSettingsString } from '../../utils/BitFields.js'; +import { emojis, colors } from '../../utils/Constants.js'; +import { hubs } from '@prisma/client'; +import { CustomID } from '../../structures/CustomID.js'; + +export function buildSettingsEmbed(hub: hubs) { + const settings = new HubSettingsBitField(hub.settings); + const settingDescriptions = { + Reactions: '**Reactions** - Allow users to react to messages.', + HideLinks: '**Hide Links** - Redact links sent by users.', + BlockInvites: '**Block Invites** - Prevent users from sending Discord invites.', + BlockNSFW: '**Block NSFW** - Detect and block NSFW images (static only).', + SpamFilter: '**Spam Filter** - Automatically blacklist spammers for 5 minutes.', + UseNicknames: '**Use Nicknames** - Use server nicknames as the network usernames.', + }; + + return new EmbedBuilder() + .setAuthor({ name: `${hub.name} Settings`, iconURL: hub.iconUrl }) + .setDescription( + Object.entries(settingDescriptions) + .map(([key, value]) => { + const flag = settings.has(key as HubSettingsString); + return `- ${flag ? emojis.enabled : emojis.disabled} ${value}`; + }) + .join('\n'), + ) + .setFooter({ text: 'Use the select menu below to toggle.' }) + .setColor(colors.interchatBlue) + .setTimestamp(); +} + +export function buildSettingsMenu(hubSettings: HubSettingsBitField, hubName: string, userId: Snowflake) { + return new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId( + new CustomID() + .setIdentifier('hub_settings', 'settings') + .addArgs(hubName) + .addArgs(userId) + .toString(), + ) + .setPlaceholder('Select an option') + .addOptions( + Object.keys(HubSettingsBitField.Flags).map((key) => { + const flag = hubSettings.has(key as HubSettingsString); + const emoji = flag ? emojis.no : emojis.yes; + return { + label: `${flag ? 'Disable' : 'Enable'} ${key}`, + value: key, + emoji, + }; + }), + ), + ); +} diff --git a/src/structures/NetworkLogger.ts b/src/structures/NetworkLogger.ts new file mode 100644 index 00000000..2fab79e3 --- /dev/null +++ b/src/structures/NetworkLogger.ts @@ -0,0 +1,196 @@ +import db from '../utils/Db.js'; +import { stripIndents } from 'common-tags'; +import { EmbedBuilder, User, Guild } from 'discord.js'; +import { emojis, colors } from '../utils/Constants.js'; +import { toTitleCase } from '../utils/Utils.js'; +import SuperClient from '../SuperClient.js'; +import BlacklistManager from '../managers/BlacklistManager.js'; + +export default class NetworkLogger { + private readonly client = SuperClient.getInstance(); + + private hubId: string; + private profanityChannelId?: string; + private modChannelId?: string; + private reportsChannelId?: string; + + constructor(hubId: string) { + this.hubId = hubId; + } + + set setProfanityChannel(channelId: string) { + db.hubs + .update({ + where: { id: this.hubId }, + data: { logChannels: { set: { profanity: channelId } } }, + }) + .then(void 0); + this.profanityChannelId = channelId; + } + set setModChannel(channelId: string) { + db.hubs + .update({ where: { id: this.hubId }, data: { logChannels: { set: { modLogs: channelId } } } }) + .then(void 0); + this.modChannelId = channelId; + } + set setReportsChannel(channelId: string) { + db.hubs + .update({ where: { id: this.hubId }, data: { logChannels: { set: { reports: channelId } } } }) + .then(void 0); + this.reportsChannelId = channelId; + } + set setHubId(hubId: string) { + this.hubId = hubId; + } + + async getReportsChannelId() { + if (this.reportsChannelId) { + return this.reportsChannelId; + } + else { + const hub = await db.hubs.findFirst({ where: { id: this.hubId } }); + this.reportsChannelId = hub?.logChannels?.reports ?? undefined; + return this.reportsChannelId; + } + } + async getModChannelId() { + if (this.modChannelId) { + return this.modChannelId; + } + else { + const hub = await db.hubs.findFirst({ where: { id: this.hubId } }); + this.modChannelId = hub?.logChannels?.modLogs ?? undefined; + return this.modChannelId; + } + } + async getProfanityChannelId() { + if (this.profanityChannelId) { + return this.profanityChannelId; + } + else { + const hub = await db.hubs.findFirst({ where: { id: this.hubId } }); + this.profanityChannelId = hub?.logChannels?.profanity ?? undefined; + return this.profanityChannelId; + } + } + + async log(channelId: string, embed: EmbedBuilder) { + this.client.cluster.broadcastEval( + async (client, ctx) => { + const channel = await client.channels.fetch(ctx.channelId).catch(() => null); + if (!channel || !channel.isTextBased()) return; + + await channel.send({ embeds: [ctx.embed] }).catch(() => null); + }, + { context: { channelId, embed } }, + ); + } + + async logProfanity(rawContent: string, author: User, server: Guild) { + const hub = await db.hubs.findFirst({ where: { id: this.hubId } }); + const embed = new EmbedBuilder() + .setTitle('Profanity Detected') + .setDescription(`||${rawContent}||`) + .addFields({ + name: 'Details', + value: stripIndents` + - Author: @${author.username} (${author.id}) + - Server: ${server.name} (${server.id}}) + - Hub: ${hub?.name} + `, + }); + + const channelId = await this.getProfanityChannelId(); + if (!channelId) return; + + return await this.log(channelId, embed); + } + + async logBlacklist(userOrServer: User | Guild, mod: User, reason: string, expires?: Date) { + const hub = await db.hubs.findFirst({ where: { id: this.hubId } }); + const name = userOrServer instanceof User ? userOrServer.username : userOrServer.name; + const iconURL = + userOrServer instanceof User + ? userOrServer.displayAvatarURL() + : userOrServer.iconURL() ?? undefined; + const type = userOrServer instanceof User ? 'User' : 'Server'; + + const embed = new EmbedBuilder() + .setAuthor({ name: `${type} ${name} blacklisted`, iconURL }) + .setDescription( + stripIndents` + ${emojis.dotBlue} **User:** ${name} (${userOrServer.id}) + ${emojis.dotBlue} **Moderator:** ${mod.username} (${mod.id}) + ${emojis.dotBlue} **Hub:** ${hub?.name} + `, + ) + .addFields( + { name: 'Reason', value: '```\n' + reason + '```', inline: true }, + { + name: 'Expires', + value: expires ? `` : 'Never.', + inline: true, + }, + ) + .setColor(colors.interchatBlue) + .setFooter({ text: `Blacklisted by: ${mod.username}`, iconURL: mod.displayAvatarURL() }); + + const channelId = await this.getModChannelId().catch(() => null); + if (!channelId) return; + + await this.log(channelId, embed); + } + + async logUnblacklist( + type: 'user' | 'server', + userOrServerId: string, + mod: User, + opts?: { reason?: string; }, + ) { + const hub = await db.hubs.findFirst({ where: { id: this.hubId } }); + + let name: string | undefined; + let blacklisted; + let originalReason: string | undefined = undefined; + + if (type === 'user') { + blacklisted = await BlacklistManager.fetchUserBlacklist(this.hubId, userOrServerId); + name = (await this.client.users.fetch(userOrServerId).catch(() => null))?.username ?? blacklisted?.username; + originalReason = blacklisted?.hubs.find(h => h.hubId === this.hubId)?.reason; + } + else { + blacklisted = await BlacklistManager.fetchServerBlacklist(this.hubId, userOrServerId); + name = blacklisted?.serverName; + } + + + const embed = new EmbedBuilder() + .setAuthor({ name: `${toTitleCase(type)} ${name} unblacklisted` }) + .setDescription( + stripIndents` + ${emojis.dotBlue} **User:** ${name} (${userOrServerId}) + ${emojis.dotBlue} **Moderator:** ${mod.username} (${mod.id}) + ${emojis.dotBlue} **Hub:** ${hub?.name} + `, + ) + .addFields( + { name: 'Reason for Unblacklist', value: opts?.reason ?? 'No reason provided.' }, + { name: 'Blacklisted For', value: originalReason ?? 'Unknown' }, + ) + .setColor(colors.interchatBlue) + .setFooter({ text: `Unblacklisted by: ${mod.username}`, iconURL: mod.displayAvatarURL() }); + + const channelId = await this.getModChannelId(); + if (!channelId) return; + + await this.log(channelId, embed); + } + + async logReports() { + // const channelId = await this.getReportsChannelId() + // if (!channelId) return; + // TODO: make it mandatory for hubs to set a report channel + // and support server + // await this.log(channelId, embed); + } +} diff --git a/src/utils/JSON/emojis.json b/src/utils/JSON/emojis.json index 2eaf4b43..618a6a60 100644 --- a/src/utils/JSON/emojis.json +++ b/src/utils/JSON/emojis.json @@ -6,7 +6,6 @@ "discordStaff": "<:badge_InterChatStaff:1102229356379648011>", "invite": "<:add:1032318201134067722>", "clipart": "<:chat_clipart:772393314413707274>", - "checkGreen": "", "tada": "", "shinyStaff": "", "enabled": " <:enabled:991180941311619205>", diff --git a/src/utils/Profanity.ts b/src/utils/Profanity.ts index d0ec5577..4c845baf 100644 --- a/src/utils/Profanity.ts +++ b/src/utils/Profanity.ts @@ -8,39 +8,36 @@ const require = createRequire(import.meta.url); const badwords = require('./JSON/profanity.json') as typeof badwordsType; /** - * Checks if a message contains any bad words. -*/ + * Checks if a message contains any bad words. + */ export function check(string: string | undefined) { - if (!string) return false; - return badwords.profanity.some(word => string.split(/\b/).some(w => w.toLowerCase() === word.toLowerCase())); + if (!string) return { profanity: false, slurs: false }; + const profanity = badwords.profanity.some((word) => + string.split(/\b/).some((w) => w.toLowerCase() === word.toLowerCase()), + ); + const slurs = badwords.slurs.some((word) => + string.split(/\b/).some((w) => w.toLowerCase() === word.toLowerCase()), + ); + + return { profanity, slurs }; } /** - * If the message contains bad words, it will be censored with asterisk(*). - * - * Code referenced from [`@web-mech/badwords`](https://github.com/web-mech/badwords). -*/ + * If the message contains bad words, it will be censored with asterisk(*). + * + * Code referenced from [`@web-mech/badwords`](https://github.com/web-mech/badwords). + */ export function censor(message: string): string { const splitRegex = /\b/; const specialChars = /[^a-zA-Z0-9|$|@]|\^/g; const matchWord = /\w/g; // filter bad words from message // and replace it with * - return message.split(splitRegex).map(word => { - return check(word) ? word.replace(specialChars, '').replace(matchWord, '\\*') : word; - }).join(splitRegex.exec(message)?.at(0)); + return message + .split(splitRegex) + .map((word) => { + const { profanity, slurs } = check(word); + return profanity || slurs ? word.replace(specialChars, '').replace(matchWord, '\\*') : word; + }) + .join(splitRegex.exec(message)?.at(0)); } - - -/** A function that can be used to send a log of an ***uncensored*** message to the log channel. */ -// export async function log(rawContent: string, author: User, guildId: string | null, hubId: string) { -// const logChan = await author.client.channels.fetch(constants.channel.networklogs) as TextChannel; -// const hubName = await getHubName(hubId).catch(() => 'Unknown'); -// const guildName = getGuildName(author.client, guildId); -// const logEmbed = new EmbedBuilder() -// .setAuthor({ name: `${author.client.user?.username} logs`, iconURL: author.client.user?.avatarURL()?.toString() }) -// .setTitle('Bad Word Detected') -// .setColor(constants.colors.invisible) -// .setDescription(`||${rawContent}||\n\n**Author:** @${author.username} \`(${author.id})\`\n**Server:** ${guildName} (${guildId})\n**Hub:** ${hubName}`); -// return await logChan?.send({ embeds: [logEmbed] }); -// } diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 539c2e63..ecb9f65a 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -16,6 +16,8 @@ import { DeveloperIds, REGEX, StaffIds, SupporterIds, URLs, colors } from './Con import { randomBytes } from 'crypto'; import Scheduler from '../services/SchedulerService.js'; import db from './Db.js'; +import startCase from 'lodash/startCase.js'; +import toLower from 'lodash/toLower.js'; /** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ export function msToReadable(milliseconds: number): string { @@ -195,3 +197,7 @@ export async function checkAndFetchImgurUrl(url: string): Promise { expect(result).toBe('https://i.imgur.com/uoRJPwW.gif'); }); - it('should return Imgur URL for valid i.imgur.com URLs', async () => { - const url = 'https://i.imgur.com/uoRJPwW.gif'; + it('should return Imgur URL for other links containing imgur.com link', async () => { + const url = 'https://i.imgur.com/uoRJPwW.gifhttps://images-ext-2.discordapp.net/external/WnWKgbKlgzwldrUZwAdI2aazoE_OirSHiMp7FDly3yA/https/i.imgur.com/SqxbMDm.png?width=493&height=246'; const result = await (await import('../src/utils/Utils.js')).checkAndFetchImgurUrl(url); expect(result).toBe(url); }); From 3880ded4a4a46208ae17b88e66f7c3125b84ecf6 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sun, 5 Nov 2023 17:03:05 +0530 Subject: [PATCH 11/13] fix: change shard count per cluster --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 7219e2c9..353449d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ const manager = new ClusterManager('build/InterChat.js', { totalShards: 'auto', mode: 'process', token: process.env.TOKEN, - shardsPerClusters: 1, + shardsPerClusters: 2, }); manager.spawn({ timeout: -1 }); From 35baaf868f0aaab2e49330443daef1bb37565c2d Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sun, 5 Nov 2023 17:25:19 +0530 Subject: [PATCH 12/13] fix some logging stuff --- src/commands/slash/Main/hub/index.ts | 2 +- src/commands/slash/Main/hub/logging.ts | 9 ++++++--- src/structures/NetworkLogger.ts | 7 ++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts index c12626a3..e2cb78b6 100644 --- a/src/commands/slash/Main/hub/index.ts +++ b/src/commands/slash/Main/hub/index.ts @@ -273,7 +273,7 @@ export default class Hub extends BaseCommand { { type: ApplicationCommandOptionType.Subcommand, name: 'logging', - description: '📁 Enable/disable logging for your hub.', + description: '📁 Enable/disable logging for your hub. (EXPERIMENTAL)', // TODO remove experimental once finished options: [ hubOption, { diff --git a/src/commands/slash/Main/hub/logging.ts b/src/commands/slash/Main/hub/logging.ts index 9227943f..7f46412a 100644 --- a/src/commands/slash/Main/hub/logging.ts +++ b/src/commands/slash/Main/hub/logging.ts @@ -4,6 +4,7 @@ import Hub from './index.js'; import { colors, emojis } from '../../../../utils/Constants.js'; import { errorEmbed } from '../../../../utils/Utils.js'; import { Prisma } from '@prisma/client'; +import { stripIndents } from 'common-tags'; export default class Logging extends Hub { async execute(interaction: ChatInputCommandInteraction) { @@ -48,9 +49,11 @@ export default class Logging extends Hub { }); const embed = new EmbedBuilder() - .setTitle('Log Channel Set') - .setDescription( - `${emojis.yes} ${channel} will be used for sending \`${type}\` logs from now on.`, + .setDescription(stripIndents` + ### <:beta:1170691588607983699> Log Channel Set + + ${emojis.yes} ${channel} will be used for sending \`${type}\` logs from now on. + `, ) .setColor(colors.invisible); diff --git a/src/structures/NetworkLogger.ts b/src/structures/NetworkLogger.ts index 2fab79e3..a736b05f 100644 --- a/src/structures/NetworkLogger.ts +++ b/src/structures/NetworkLogger.ts @@ -91,12 +91,13 @@ export default class NetworkLogger { const embed = new EmbedBuilder() .setTitle('Profanity Detected') .setDescription(`||${rawContent}||`) + .setColor(colors.interchatBlue) .addFields({ name: 'Details', value: stripIndents` - - Author: @${author.username} (${author.id}) - - Server: ${server.name} (${server.id}}) - - Hub: ${hub?.name} + ${emojis.dotBlue} **Author:** @${author.username} (${author.id}) + ${emojis.dotBlue} **Server:** ${server.name} (${server.id}}) + ${emojis.dotBlue} **Hub:** ${hub?.name} `, }); From 2e37f8132a9fe567e5b8789effe16f56eba6ac7c Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sun, 5 Nov 2023 17:30:38 +0530 Subject: [PATCH 13/13] fix deploy script --- .github/workflows/build.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5c0fe03..9c40f763 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,12 +29,16 @@ jobs: uses: actions/checkout@v3 - name: Install dependencies - run: npm install + # temporary fix for installing deps for api + run: | + npm install + cd api && npm install - name: Build code run: | - cp ~/important/interchat-env .env - npm run build --if-present + npm run build --if-present + cd .. && npm run build --if-present + cp ~/important/interchat-env .env - name: Deploy Commands run: npm run deploy -- -b