From 9854f3c60fc8913019ce40d0cbcfb7c69801f724 Mon Sep 17 00:00:00 2001 From: Vylpes Date: Sun, 25 Apr 2021 16:50:27 +0100 Subject: [PATCH 01/28] Change rules.txt to rules.json (#31) --- commands/rules.js | 43 ++++++++++------------ config.template.json | 8 ++--- data/rules/rules.json | 84 +++++++++++++++++++++++++++++++++++++++++++ data/rules/rules.txt | 41 --------------------- package-lock.json | 4 +-- 5 files changed, 109 insertions(+), 71 deletions(-) create mode 100644 data/rules/rules.json delete mode 100644 data/rules/rules.txt diff --git a/commands/rules.js b/commands/rules.js index 14afa82..266e504 100644 --- a/commands/rules.js +++ b/commands/rules.js @@ -25,35 +25,30 @@ class rules extends command { if (context.message.member.roles.cache.find(role => role.name == context.client.config.rules.adminrole)) { // If the rulesfile exists if (existsSync(context.client.config.rules.rulesfile)) { - // Get the contents of the rules file, and split it by "> " - // Each embed in the rules is set by the "> " syntax - let rulesText = readFileSync(context.client.config.rules.rulesfile).toString(); - rulesText = rulesText.split("> "); + const rulesJson = readFileSync(context.client.config.rules.rulesfile); + const rules = JSON.parse(rulesJson); - // Loop through each embed to be sent - for (let i = 0; i < rulesText.length; i++) { - // If the first line after "> " has a "#", create and embed with an image of the url specified after - if (rulesText[i].charAt(0) == '#') { - const embed = new MessageEmbed() - .setColor(embedColor) - .setImage(rulesText[i].substring(1)); + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + const embed = new MessageEmbed(); - context.message.channel.send(embed); - } else { // If the file doesn't have a "#" at the start - // Split the embed into different lines, set the first line as the title, and the rest as the description - const rulesLines = rulesText[i].split("\n"); - const rulesTitle = rulesLines[0]; - const rulesDescription = rulesLines.slice(1).join("\n"); + embed.setColor(embedColor); - // Create the embed with the specified information above - const embed = new MessageEmbed() - .setTitle(rulesTitle) - .setColor(embedColor) - .setDescription(rulesDescription); + if (rule.image) embed.setImage(rule.image); + if (rule.title) embed.setTitle(rule.title); + if (rule.footer) embed.setFooter(rule.footer); + if (rule.description) { + let description = ""; - // Send the embed - context.message.channel.send(embed); + for (let j = 0; j < rule.description.length; j++) { + const line = rule.description[j]; + description += `${line}\n`; + } + + embed.setDescription(description); } + + context.message.channel.send(embed); } } else { // If the rules file doesn't exist const errorEmbed = new MessageEmbed() diff --git a/config.template.json b/config.template.json index 1be9777..2739903 100644 --- a/config.template.json +++ b/config.template.json @@ -1,6 +1,6 @@ { "token": "", - "prefix": "d!", + "prefix": "v!", "commands": [ "commands" ], @@ -9,10 +9,10 @@ ], "about": { "description": "Discord Bot for Vylpes' Den", - "version": "2.1", + "version": "2.2", "core-ver": "1.0.4", "author": "Vylpes", - "date": "17-Feb-21" + "date": "22/04/2021" }, "ban": { "modrole": "Moderator", @@ -40,7 +40,7 @@ }, "rules": { "adminrole": "Admin", - "rulesfile": "data/rules/rules.txt" + "rulesfile": "data/rules/rules.json" }, "unmute": { "modrole": "Moderator", diff --git a/data/rules/rules.json b/data/rules/rules.json new file mode 100644 index 0000000..e65a2ce --- /dev/null +++ b/data/rules/rules.json @@ -0,0 +1,84 @@ +[ + { + "image": "https://i.imgur.com/bjH1gza.png" + }, + { + "title": "Vylpes' Den", + "description": [ + "Welcome to Vylpes' Den! Make sure to say hi!", + "Invite link: https://discord.gg/UyAhAVp" + ] + }, + { + "title": "Discord TOS", + "description": [ + "All servers are required to follow the Discord Terms of Service. This includes minimum age requirements (13+). If the moderation team discover a breach of TOS we are required by discord to ban. Make sure you know them!", + "https://discord.com/terms" + ] + }, + { + "title": "Rules", + "description": [ + "**English Only**", + "In order for everyone to understand each other we would like to ask everyone to speak in English only.", + "", + "**No NSFW or Obscene Content**", + "This includes text, images, or links featuring nudity, sex, hard violence, or other graphically disturbing content.", + "", + "**Treat Everyone with Respect**", + "Absolutely no harassment, witch hunting, sexism, racism, or hate speech will be tolerated.", + "", + "**No spam or self promotion**", + "Outside of #self-promo. This includes DMing fellow members.", + "", + "**Keep Politics to #general**", + "And make sure it doesn't become too heated. Debate don't argue.", + "", + "**Drama From Other Servers**", + "Please don't bring up drama from other servers, keep that to DMs", + "", + "**Bot Abuse**", + "Don't abuse the bots or you will be blocked from using them" + ] + }, + { + "title": "Moderators Discretion", + "description": [ + "Don't argue with a mod's decision. A moderator's choice is final. If you have an issue with a member of the mod team DM me (Vylpes#0001)." + ] + }, + { + "title": "Supporters", + "description": [ + "If you are a Twitch Subscriber or a Patreon Member and have linked your profiles to your discord account you will get exclusive access to the Vylpes Plus channels, including early access to videos!" + ] + }, + { + "title": "Self-Assignable Roles", + "description": [ + "If you want to assign yourself roles, go to #bot-stuff and type v!role . The current roles you can get are:", + "Notify: Get pinged when a new stream or video releases.", + "VotePings: Get pinged when I start a new poll", + "ProjectUpdates: Get pinged when I update my projects as well as new for them" + ] + }, + { + "title": "VylBot", + "description": [ + "This server uses a bot made by me, VylBot, to help moderate the server.", + "For more information on it, see the GitHub repositories:", + "https://github.com/Vylpes/vylbot-core", + "https://github.com/Vylpes/vylbot-app" + ] + }, + { + "title": "Links", + "description": [ + "YouTube: https://www.youtube.com/channel/UCwPlzKwCmP5Q9bCX3fHk2BA", + "Patreon: https://www.patreon.com/vylpes", + "Twitch: https://www.twitch.tv/vylpes_", + "Twitter: https://twitter.com/vylpes" + ], + "footer": "Last updated 22/04/2021" + } +] \ No newline at end of file diff --git a/data/rules/rules.txt b/data/rules/rules.txt deleted file mode 100644 index 071557a..0000000 --- a/data/rules/rules.txt +++ /dev/null @@ -1,41 +0,0 @@ -> #https://i.imgur.com/bjH1gza.png -> Vylpes' Den -Welcome to Vylpes' Den! Make sure to say hi! -Invite link: https://discord.gg/UyAhAVp -> Discord TOS -All servers are required to follow the Discord Terms of Service. This includes minimum age requirements (13+). If the moderation team discover a breach of TOS we are required by discord to ban. Make sure you follow them! - https://discord.com/terms -> Rules -- **English Only** -In order for everyone to understand each other we would like to ask everyone to speak in English only. - -- **No NSFW or Obscene Content** -This includes text, images, or links featuring nudity, sex, hard violence, or other graphically disturbing content. - -- **Treat Everyone with Respect** -Absolutely no harassment, witch hunting, sexism, racism, or hate speech will be tolerated. - -- **No spam or self promotion** -Outside of #self-promo. This includes DMing fellow members. - -- **Keep Politics to #general** -And make sure it doesn't become too heated. Debate don't argue. - -- **Drama From Other Servers** -Please don't bring up drama from other servers, keep that to DMs - -- **Bot Abuse** -Don't abuse the bots or you will be blocked from using them - -> Moderators Discretion -Don't argue with a mod's decision. A moderator's choice is final. If you have an issue with a member of the mod team DM me (Vylpes#0001). -> Supporters -If you are a Twitch Subscriber or a Patreon Member and have linked your profiles to your discord account you will get exclusive access to the Vylpes Plus channels, including early access to videos! -> Self-Assignable Roles -If you want to assign yourself roles, go to #bot-stuff and type v!role . The current roles you can get are: -Notify: Get pinged when a new stream or video releases. -> Links -YouTube: https://www.youtube.com/channel/UCwPlzKwCmP5Q9bCX3fHk2BA -Patreon: https://www.patreon.com/vylpes -Twitch: https://www.twitch.tv/vylpes_ -Twitter: https://twitter.com/vylpes - diff --git a/package-lock.json b/package-lock.json index a9c8367..28fa8b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "vylbot-app", - "version": "1.0.0", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.0.0", + "version": "2.1.0", "license": "ISC", "dependencies": { "emoji-regex": "^9.2.0", From 38a5f6fb29a8d7c0c3b8887ecfc839c8bf94a551 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 22 Nov 2021 20:59:49 +0000 Subject: [PATCH 02/28] Migrate to yarn --- package-lock.json | 2483 --------------------------------------------- yarn.lock | 895 ++++++++++++++++ 2 files changed, 895 insertions(+), 2483 deletions(-) delete mode 100644 package-lock.json create mode 100644 yarn.lock diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 7cb01f4..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2483 +0,0 @@ -{ - "name": "vylbot-app", - "version": "2.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "version": "2.1.0", - "license": "ISC", - "dependencies": { - "emoji-regex": "^9.2.0", - "random-bunny": "^1.0.0", - "vylbot-core": "^1.0.4" - }, - "devDependencies": { - "eslint": "^7.17.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "node_modules/@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/highlight/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/@babel/highlight/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/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - }, - "node_modules/@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", - "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.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==", - "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/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "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/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "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/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/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/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": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": 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/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "dependencies": { - "@discordjs/collection": "^0.1.6", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.1", - "prism-media": "^1.2.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - }, - "engines": { - "node": ">=12.0.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/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.26.0.tgz", - "integrity": "sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.21", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "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": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": 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==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "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, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/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": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "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": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "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==", - "dev": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "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": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dependencies": { - "mime-db": "1.47.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prism-media": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", - "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==", - "peerDependencies": { - "@discordjs/opus": "^0.5.0", - "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", - "node-opus": "^0.3.3", - "opusscript": "^0.0.8" - }, - "peerDependenciesMeta": { - "@discordjs/opus": { - "optional": true - }, - "ffmpeg-static": { - "optional": true - }, - "node-opus": { - "optional": true - }, - "opusscript": { - "optional": true - } - } - }, - "node_modules/progress": { - "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/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/random-bunny": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/random-bunny/-/random-bunny-1.2.1.tgz", - "integrity": "sha512-qimyihq19RSZy+8uQKEgBJ8u5NeCDE8uoMeoBycsYgGmoPERAPI9mYSYW0vd+Qj4PpRYhrPIerCnsXRfnx2/Gg==", - "dependencies": { - "node-fetch": "^2.6.1" - }, - "funding": { - "url": "https://ko-fi.com/vylpes" - } - }, - "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "engines": { - "node": ">=4" - } - }, - "node_modules/rimraf": { - "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" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "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==", - "dev": true, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/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/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/table": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.0.tgz", - "integrity": "sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.3.0.tgz", - "integrity": "sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q==", - "dev": 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/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/vylbot-core": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vylbot-core/-/vylbot-core-1.0.4.tgz", - "integrity": "sha512-KS2+Tl8RiXwhxMj2ja3c3rWXDlY5n7458Z+o29szweoz+UjX9GmCseIcy5iajEv6Snrh4kYe5PE8JJ5jfT2svw==", - "dependencies": { - "discord.js": "^12.3.1" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - }, - "@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "@eslint/eslintrc": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", - "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "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, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "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, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "requires": { - "@discordjs/collection": "^0.1.6", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.1", - "prism-media": "^1.2.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.26.0.tgz", - "integrity": "sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.21", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "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 - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "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, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "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==", - "dev": true - }, - "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 - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "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, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "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, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "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 - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "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, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "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==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "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, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "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 - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "requires": { - "mime-db": "1.47.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prism-media": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", - "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==", - "requires": {} - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "random-bunny": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/random-bunny/-/random-bunny-1.2.1.tgz", - "integrity": "sha512-qimyihq19RSZy+8uQKEgBJ8u5NeCDE8uoMeoBycsYgGmoPERAPI9mYSYW0vd+Qj4PpRYhrPIerCnsXRfnx2/Gg==", - "requires": { - "node-fetch": "^2.6.1" - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "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 - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "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==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "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 - } - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - }, - "table": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.0.tgz", - "integrity": "sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.3.0.tgz", - "integrity": "sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "vylbot-core": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vylbot-core/-/vylbot-core-1.0.4.tgz", - "integrity": "sha512-KS2+Tl8RiXwhxMj2ja3c3rWXDlY5n7458Z+o29szweoz+UjX9GmCseIcy5iajEv6Snrh4kYe5PE8JJ5jfT2svw==", - "requires": { - "discord.js": "^12.3.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", - "requires": {} - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } -} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e3e84ea --- /dev/null +++ b/yarn.lock @@ -0,0 +1,895 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.15.7": + version "7.15.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== + +"@babel/highlight@^7.10.4": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@discordjs/collection@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c" + integrity sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ== + +"@discordjs/form-data@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@discordjs/form-data/-/form-data-3.0.1.tgz#5c9e6be992e2e57d0dfa0e39979a850225fb4697" + integrity sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + 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" + +ajv@^8.0.1: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" + integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.0.1, debug@^4.1.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +discord.js@^12.3.1: + version "12.5.3" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.5.3.tgz#56820d473c24320871df9ea0bbc6b462f21cf85c" + integrity sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw== + dependencies: + "@discordjs/collection" "^0.1.6" + "@discordjs/form-data" "^3.0.1" + abort-controller "^3.0.0" + node-fetch "^2.6.1" + prism-media "^1.2.9" + setimmediate "^1.0.5" + tweetnacl "^1.0.3" + ws "^7.4.4" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.0: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.17.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.6.0, globals@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== + dependencies: + type-fest "^0.20.2" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + +mime-types@^2.1.12: + version "2.1.34" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + dependencies: + mime-db "1.51.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +node-fetch@^2.6.1: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prism-media@^1.2.9: + version "1.3.2" + resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.2.tgz#a1f04423ec15d22f3d62b1987b6a25dc49aad13b" + integrity sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +random-bunny@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/random-bunny/-/random-bunny-1.2.2.tgz#821882ba33b6be05e3a538c43e822e5e9896862d" + integrity sha512-m12WIb4uNeEA8eLk9vxK6U9x/+KlRwBqOoiuGku6C6kF6DcbyMTq0RaRUXF3fXxpRtZbpoU7dgttLoETTXXL/g== + dependencies: + glob-parent "^6.0.0" + node-fetch "^2.6.1" + +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +semver@^7.2.1: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + 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" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^6.0.9: + version "6.7.3" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" + integrity sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +vylbot-core@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-1.0.5.tgz#44a0a38d3c8bfda38883cab1a16e5e15435c98d7" + integrity sha512-N86HpZnQadi+d4w4ZaTcbSRAjRInhzG8BN/WDNGUvFjF2UuA8SUKfd9Wk6rbs7mLLyXw+VFFLYdDbe0Q6ZfNYA== + dependencies: + discord.js "^12.3.1" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^7.4.4: + version "7.5.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== From ee7fe3fd1930e6ee36f67eccc3010600d0f42ad5 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Tue, 23 Nov 2021 11:41:40 +0000 Subject: [PATCH 03/28] Add role configs to config template --- config.template.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/config.template.json b/config.template.json index 2739903..eb86869 100644 --- a/config.template.json +++ b/config.template.json @@ -12,7 +12,7 @@ "version": "2.2", "core-ver": "1.0.4", "author": "Vylpes", - "date": "22/04/2021" + "date": "23/11/2021" }, "ban": { "modrole": "Moderator", @@ -50,5 +50,12 @@ "warn": { "modrole": "Moderator", "logchannel": "mod-logs" + }, + "role": { + "assignable": [ + "Notify", + "VotePings", + "ProjectUpdates" + ] } } \ No newline at end of file From 6fb2da2b1881323cb1529fc119c99f1095665efc Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sun, 28 Nov 2021 14:24:37 +0000 Subject: [PATCH 04/28] Install packges and setup typescript --- .env.template | 23 +++++++++++ package.json | 28 +++++++------ tsconfig.json | 78 ++++++++++++++++++++++++++++++++++++ yarn.lock | 109 ++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 221 insertions(+), 17 deletions(-) create mode 100644 .env.template create mode 100644 tsconfig.json diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..351704b --- /dev/null +++ b/.env.template @@ -0,0 +1,23 @@ +# Security Warning! Do not commit this file to any VCS! +# This is a local file to speed up development process, +# so you don't have to change your environment variables. +# +# This is not applied to `.env.template`! +# Template files must be committed to the VCS, but must not contain +# any secret values. + +BOT_TOKEN= +BOT_PREFIX=v! +BOT_VER=3.0 +BOT_AUTHOR=Vylpes +BOT_DATE=28 Nov 2021 + +CORE_VER=2.0.2 + +FOLDERS_COMMANDS=commands +FOLDERS_EVENTS=events + +COMMANDS_DISABLED= +COMMANDS_DISABLED_MESSAGE=This command is disabled. + +EMBED_COLOUR=0x3050ba \ No newline at end of file diff --git a/package.json b/package.json index 5420a50..0db313d 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,31 @@ { "name": "vylbot-app", - "version": "2.1.1", - "description": "", - "main": "vylbot.js", + "version": "3.0", + "description": "A discord bot made for Vylpes' Den", + "main": "./dist/vylbot", + "typings": "./dist", "scripts": { - "start": "node vylbot.js", - "lint": "eslint .", - "lint:fix": "eslint . --fix" + "build": "tsc", + "start": "ts-node ./src/vylbot" }, "repository": { "type": "git", "url": "git+https://github.com/Vylpes/vylbot-app.git" }, "author": "Vylpes", - "license": "ISC", - "bugs": { - "url": "https://github.com/Vylpes/vylbot-app/issues" - }, - "homepage": "https://github.com/Vylpes/vylbot-app#readme", + "license": "MIT", + "bugs": "https://github.com/Vylpes/vylbot-app/issues", + "homepage": "https://github.com/Vylpes/vylbot-app", "dependencies": { + "dotenv": "^10.0.0", "emoji-regex": "^9.2.0", "random-bunny": "^1.0.0", - "vylbot-core": "^1.0.4" + "vylbot-core": "^2.0.2" }, "devDependencies": { - "eslint": "^7.17.0" + "@types/node": "^16.11.10", + "eslint": "^7.17.0", + "ts-node": "^10.4.0", + "typescript": "^4.5.2" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3f030e6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,78 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": [ + "./src", + ], + "exclude": [ + "./tests" + ] +} diff --git a/yarn.lock b/yarn.lock index e3e84ea..6a27750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,18 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@discordjs/collection@^0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c" @@ -66,6 +78,31 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + +"@types/node@^16.11.10": + version "16.11.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.10.tgz#2e3ad0a680d96367103d3e670d41c2fed3da61ae" + integrity sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -78,11 +115,21 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.4.1: + version "8.6.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" + integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -127,6 +174,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -215,6 +267,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -241,6 +298,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + discord.js@^12.3.1: version "12.5.3" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.5.3.tgz#56820d473c24320871df9ea0bbc6b462f21cf85c" @@ -262,6 +324,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -602,6 +669,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + mime-db@1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" @@ -818,6 +890,24 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-node@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" + integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + yn "3.1.1" + tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" @@ -835,6 +925,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +typescript@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" + integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -847,12 +942,13 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vylbot-core@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-1.0.5.tgz#44a0a38d3c8bfda38883cab1a16e5e15435c98d7" - integrity sha512-N86HpZnQadi+d4w4ZaTcbSRAjRInhzG8BN/WDNGUvFjF2UuA8SUKfd9Wk6rbs7mLLyXw+VFFLYdDbe0Q6ZfNYA== +vylbot-core@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-2.0.2.tgz#8f83b836b423e9f0812abb86368672716dc1fa75" + integrity sha512-Og+cdPG+ExjzjhqVDb1hac7pm6KfLvT85NMBoL7hfbSPsg11wFwLKI6toqVnbvbqMKzKnOpIvijo4jx+fdUDsA== dependencies: discord.js "^12.3.1" + dotenv "^10.0.0" webidl-conversions@^3.0.0: version "3.0.1" @@ -893,3 +989,8 @@ yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From c7417cf7a563c0e552949b5b759fdc493971be7f Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sun, 28 Nov 2021 14:24:53 +0000 Subject: [PATCH 05/28] Migrate entry point --- src/vylbot.ts | 9 +++++++++ vylbot.js | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/vylbot.ts delete mode 100644 vylbot.js diff --git a/src/vylbot.ts b/src/vylbot.ts new file mode 100644 index 0000000..73bd1df --- /dev/null +++ b/src/vylbot.ts @@ -0,0 +1,9 @@ +import { CoreClient } from "vylbot-core"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +if (!process.env.EMBED_COLOUR) throw "EMBED_COLOUR is required in .env"; + +const client = new CoreClient(); +client.start(); \ No newline at end of file diff --git a/vylbot.js b/vylbot.js deleted file mode 100644 index d8625ff..0000000 --- a/vylbot.js +++ /dev/null @@ -1,5 +0,0 @@ -const vylbot = require('vylbot-core'); -const config = require('./config.json'); - -const client = new vylbot.client(config); -client.start(); \ No newline at end of file From bb433749f8a06869d98df344e1c318edef5b9600 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Sun, 28 Nov 2021 14:25:00 +0000 Subject: [PATCH 06/28] Migrate about command --- commands/about.js | 45 ------------------------------------------- src/commands/about.ts | 22 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 45 deletions(-) delete mode 100644 commands/about.js create mode 100644 src/commands/about.ts diff --git a/commands/about.js b/commands/about.js deleted file mode 100644 index 5c2fbb2..0000000 --- a/commands/about.js +++ /dev/null @@ -1,45 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class about extends command { - constructor() { - // Set execute method, description, and category - super("about"); - super.description = "About the bot"; - super.category = "General"; - - // Set required configs in the config.about json string. - // description: The bot description - // version: The bot version - // author: Bot author - // date: Date of build - super.requiredConfigs = "description"; - super.requiredConfigs = "version"; - super.requiredConfigs = "core-ver"; - super.requiredConfigs = "author"; - super.requiredConfigs = "date"; - } - - // The execution method - about(context) { - // Create an embed containing data about the bot - const embed = new MessageEmbed() - .setTitle("About") - .setColor(embedColor) - .setDescription(context.client.config.about.description) - .addField("Version", context.client.config.about.version, true) - .addField("VylBot Core", context.client.config.about['core-ver'], true) - .addField("Author", context.client.config.about.author) - .addField("Date", context.client.config.about.date); - - // Send embed to the channel the command was sent in - context.message.channel.send(embed); - } -} - -// Set the about class to be exported -module.exports = about; diff --git a/src/commands/about.ts b/src/commands/about.ts new file mode 100644 index 0000000..f6b30e3 --- /dev/null +++ b/src/commands/about.ts @@ -0,0 +1,22 @@ +import { Command, ICommandContext } from "vylbot-core"; +import { MessageEmbed } from "discord.js"; + +export default class About extends Command { + constructor() { + super(); + super._category = "General"; + } + + public override execute(context: ICommandContext) { + const embed = new MessageEmbed() + .setTitle("About") + .setColor(process.env.EMBED_COLOUR!) + .setDescription("About the bot") + .addField("Version", process.env.BOT_VER) + .addField("VylBot Core", process.env.CORE_VER) + .addField("Author", process.env.BOT_AUTHOR) + .addField("Date", process.env.BOT_DATE); + + context.message.channel.send(embed); + } +} \ No newline at end of file From ecf9c5e4fc805be2db21510fa77ed5cf2d0958ad Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 29 Nov 2021 11:27:44 +0000 Subject: [PATCH 07/28] Migrate ban command --- .env.template | 13 +++-- commands/ban.js | 93 ---------------------------------- src/commands/ban.ts | 61 ++++++++++++++++++++++ src/constants/ErrorMessages.ts | 4 ++ src/helpers/ErrorEmbed.ts | 19 +++++++ src/helpers/LogEmbed.ts | 60 ++++++++++++++++++++++ src/helpers/PublicEmbed.ts | 21 ++++++++ src/vylbot.ts | 8 +++ 8 files changed, 183 insertions(+), 96 deletions(-) delete mode 100644 commands/ban.js create mode 100644 src/commands/ban.ts create mode 100644 src/constants/ErrorMessages.ts create mode 100644 src/helpers/ErrorEmbed.ts create mode 100644 src/helpers/LogEmbed.ts create mode 100644 src/helpers/PublicEmbed.ts diff --git a/.env.template b/.env.template index 351704b..bfbe4a3 100644 --- a/.env.template +++ b/.env.template @@ -14,10 +14,17 @@ BOT_DATE=28 Nov 2021 CORE_VER=2.0.2 -FOLDERS_COMMANDS=commands -FOLDERS_EVENTS=events +FOLDERS_COMMANDS=src/commands +FOLDERS_EVENTS=src/events COMMANDS_DISABLED= COMMANDS_DISABLED_MESSAGE=This command is disabled. -EMBED_COLOUR=0x3050ba \ No newline at end of file +EMBED_COLOUR=0x3050ba +EMBED_COLOUR_ERROR=0xD52803 + +ROLES_MODERATOR=Moderator + +CHANNELS_LOGS_MESSAGE=message-logs +CHANNELS_LOGS_MEMBER=member-logs +CHANNELS_LOGS_MOD=mod-logs \ No newline at end of file diff --git a/commands/ban.js b/commands/ban.js deleted file mode 100644 index c8acbb9..0000000 --- a/commands/ban.js +++ /dev/null @@ -1,93 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class ban extends command { - constructor() { - // Set execution method, description, category, and usage - super("ban"); - super.description = "Bans the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set required configs in the config.ban json string - super.requiredConfigs = "modrole"; - super.requiredCofigs = "logchannel"; - } - - // Command execution method - ban(context) { - // If the user has the modrole (set in config.ban.modrole) - if (context.message.guild.roles.cache.find(role => role.name == context.client.config.ban.modrole)) { - // Gets the user pinged in the command - const user = context.message.mentions.users.first(); - - // If the user pinged is a valid user - if (user) { - // Get the guild member object from the pinged user - const member = context.message.guild.member(user); - - // If the member object exists, i.e. if they are in the server - if (member) { - // Get the arguments and remove what isn't the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the array into a string - const reason = reasonArgs.join(" "); - - // If the guild is available to work with - if (context.message.guild.available) { - // If the bot client is able to ban the member - if (member.bannable) { - // The Message Embed which goes into the bot log - const embedLog = new MessageEmbed() - .setTitle("Member Banned") - .setColor(embedColor) - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The Message Embed which goes into the public channel the message was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been banned`); - - // Ban the member and send the embeds into the appropriate channel, then delete the initial message - member.ban({ reason: reason }).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.ban.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { // If the bot couldn't ban the member, say so and log the error to the console - errorEmbed(context, "An error occurred"); - console.log(err); - }); - } - } - } else { // If the member object doesn't exist - errorEmbed(context, "User is not in this server"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "User does not exist"); - } - } else { // If the user doesn't have the mod role - errorEmbed(context, `You require the \`${context.client.config.ban.modrole}\` role to run this command`); - } - } -} - -// Post an error embed -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = ban; diff --git a/src/commands/ban.ts b/src/commands/ban.ts new file mode 100644 index 0000000..b0a1371 --- /dev/null +++ b/src/commands/ban.ts @@ -0,0 +1,61 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import ErrorMessages from "../constants/ErrorMessages"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Bane extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1) + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.bannable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Banned"); + logEmbed.AddUser("User", targetUser, true); + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been banned`); + + await targetMember.ban({ reason: reason }); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/constants/ErrorMessages.ts b/src/constants/ErrorMessages.ts new file mode 100644 index 0000000..d3c1fe1 --- /dev/null +++ b/src/constants/ErrorMessages.ts @@ -0,0 +1,4 @@ +export default class ErrorMessages { + public static readonly InsufficientBotPermissions = "Unable to do this action, am I missing permissions?"; + public static readonly CantFindChannel = "Unable to find channel"; +} \ No newline at end of file diff --git a/src/helpers/ErrorEmbed.ts b/src/helpers/ErrorEmbed.ts new file mode 100644 index 0000000..9964369 --- /dev/null +++ b/src/helpers/ErrorEmbed.ts @@ -0,0 +1,19 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "vylbot-core"; + +export default class ErrorEmbed extends MessageEmbed { + private _context: ICommandContext; + + constructor(context: ICommandContext, message: String) { + super(); + + super.setColor(process.env.EMBED_COLOUR_ERROR!); + super.setDescription(message); + + this._context = context; + } + + public SendToCurrentChannel() { + this._context.message.channel.send(this); + } +} \ No newline at end of file diff --git a/src/helpers/LogEmbed.ts b/src/helpers/LogEmbed.ts new file mode 100644 index 0000000..5a801d1 --- /dev/null +++ b/src/helpers/LogEmbed.ts @@ -0,0 +1,60 @@ +import { MessageEmbed, TextChannel, User } from "discord.js"; +import { ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "./ErrorEmbed"; + +export default class LogEmbed extends MessageEmbed { + private _context: ICommandContext; + + constructor(context: ICommandContext, title: string) { + super(); + + super.setColor(process.env.ERROR_EMBED!); + super.setTitle(title); + + this._context = context; + } + + // Detail methods + public AddUser(title: string, user: User, setThumbnail: boolean = false) { + super.addField(title, `${user} \`${user.tag}\``, true); + + if (setThumbnail) { + super.setThumbnail(user.displayAvatarURL()); + } + } + + public AddReason(message: String) { + super.addField("Reason", message || "*none*"); + } + + // Send methods + public SendToCurrentChannel() { + this._context.message.channel.send(this); + } + + public SendToChannel(name: string) { + const channel = this._context.message.guild?.channels.cache + .find(channel => channel.name == name) as TextChannel; + + if (!channel) { + const errorEmbed = new ErrorEmbed(this._context, ErrorMessages.CantFindChannel); + errorEmbed.SendToCurrentChannel(); + return; + } + + channel.send(this); + } + + public SendToMessageLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MESSAGE!) + } + + public SendToMemberLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MEMBER!) + } + + public SendToModLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MOD!) + } +} \ No newline at end of file diff --git a/src/helpers/PublicEmbed.ts b/src/helpers/PublicEmbed.ts new file mode 100644 index 0000000..75d99fe --- /dev/null +++ b/src/helpers/PublicEmbed.ts @@ -0,0 +1,21 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "vylbot-core"; + +export default class PublicEmbed extends MessageEmbed { + private _context: ICommandContext; + + constructor(context: ICommandContext, title: string, description: string) { + super(); + + super.setColor(process.env.ERROR_EMBED!); + super.setTitle(title); + super.setDescription(description); + + this._context = context; + } + + // Send methods + public SendToCurrentChannel() { + this._context.message.channel.send(this); + } +} \ No newline at end of file diff --git a/src/vylbot.ts b/src/vylbot.ts index 73bd1df..c9b0eee 100644 --- a/src/vylbot.ts +++ b/src/vylbot.ts @@ -3,7 +3,15 @@ import * as dotenv from "dotenv"; dotenv.config(); +// Ensure required data is in dotenv if (!process.env.EMBED_COLOUR) throw "EMBED_COLOUR is required in .env"; +if (!process.env.EMBED_COLOUR_ERROR) throw "EMBED_COLOUR_ERROR is required in .env"; + +if (!process.env.ROLES_MODERATOR) throw "ROLES_MODERATOR is required in .env"; + +if (!process.env.CHANNELS_LOGS_MESSAGE) throw "CHANNELS_LOGS_MESSAGE is required in .env"; +if (!process.env.CHANNELS_LOGS_MEMBER) throw "CHANNELS_LOGS_MEMBER is required in .env"; +if (!process.env.CHANNELS_LOGS_MOD) throw "CHANNELS_LOGS_MOD is required in .env"; const client = new CoreClient(); client.start(); \ No newline at end of file From be329d709f7de21fee496fce47d829099ef6f7ce Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 29 Nov 2021 11:46:16 +0000 Subject: [PATCH 08/28] Migrate clear command --- commands/clear.js | 58 -------------------------------------- src/commands/clear.ts | 36 +++++++++++++++++++++++ src/helpers/LogEmbed.ts | 2 +- src/helpers/PublicEmbed.ts | 2 +- 4 files changed, 38 insertions(+), 60 deletions(-) delete mode 100644 commands/clear.js create mode 100644 src/commands/clear.ts diff --git a/commands/clear.js b/commands/clear.js deleted file mode 100644 index d84a288..0000000 --- a/commands/clear.js +++ /dev/null @@ -1,58 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class clear extends command { - constructor() { - // Set execute method, description, category, and usage - super("clear"); - super.description = "Bulk deletes the chat for up to 100 messages"; - super.category = "Moderation"; - super.usage = ""; - - // Set required configs in the config.clear json string - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - } - - // Execute method - clear(context) { - // If the user has the config.clear.modrole role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.clear.modrole)) { - // If the command specifies a number between 1 and 100 - if (context.arguments.length > 0 && context.arguments[0] > 0 && context.arguments[0] < 101) { - // Attempt to bulk delete the amount of messages specified as an argument - context.message.channel.bulkDelete(context.arguments[0]).then(() => { - // Public embed - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${context.arguments[0]} messages were removed`); - - // Send the embed into the channel the command was sent in - context.message.channel.send(embed); - }).catch(err => { // If the bot couldn't bulk delete - errorEmbed(context, "An error has occurred"); - console.log(err); - }); - } else { // If the user didn't give a number valid (between 1 and 100) - errorEmbed(context, "Please specify an amount between 1 and 100"); - } - } else { // If the user doesn't have the mod role - errorEmbed(context, `This command requires the \`${context.client.config.clear.modrole}\` role to run`); - } - } -} - -// Function to send an error embed -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = clear; diff --git a/src/commands/clear.ts b/src/commands/clear.ts new file mode 100644 index 0000000..14015d4 --- /dev/null +++ b/src/commands/clear.ts @@ -0,0 +1,36 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import { TextChannel } from "discord.js"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Clear extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + if (context.args.length == 0) { + const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const totalToClear = Number.parseInt(context.args[0]); + + if (!totalToClear || totalToClear <= 0 || totalToClear > 100) { + const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100"); + errorEmbed.SendToCurrentChannel(); + return; + } + + await (context.message.channel as TextChannel).bulkDelete(totalToClear); + + const embed = new PublicEmbed(context, "", `${totalToClear} message(s) were removed`); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/helpers/LogEmbed.ts b/src/helpers/LogEmbed.ts index 5a801d1..4eff943 100644 --- a/src/helpers/LogEmbed.ts +++ b/src/helpers/LogEmbed.ts @@ -9,7 +9,7 @@ export default class LogEmbed extends MessageEmbed { constructor(context: ICommandContext, title: string) { super(); - super.setColor(process.env.ERROR_EMBED!); + super.setColor(process.env.EMBED_COLOUR!); super.setTitle(title); this._context = context; diff --git a/src/helpers/PublicEmbed.ts b/src/helpers/PublicEmbed.ts index 75d99fe..790f27f 100644 --- a/src/helpers/PublicEmbed.ts +++ b/src/helpers/PublicEmbed.ts @@ -7,7 +7,7 @@ export default class PublicEmbed extends MessageEmbed { constructor(context: ICommandContext, title: string, description: string) { super(); - super.setColor(process.env.ERROR_EMBED!); + super.setColor(process.env.EMBED_COLOUR!); super.setTitle(title); super.setDescription(description); From 019966f25fb63c5ebed4379bd185b36deb93c0da Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 29 Nov 2021 11:51:54 +0000 Subject: [PATCH 09/28] Migrate kick command --- commands/kick.js | 93 -------------------------------------------- src/commands/kick.ts | 61 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 93 deletions(-) delete mode 100644 commands/kick.js create mode 100644 src/commands/kick.ts diff --git a/commands/kick.js b/commands/kick.js deleted file mode 100644 index 4878270..0000000 --- a/commands/kick.js +++ /dev/null @@ -1,93 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class kick extends command { - constructor() { - // Sets the command's run method, description, category, and usage - super("kick"); - super.description = "Kicks the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Sets the required configs for the command - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - } - - // The command's run method - kick(context) { - // Checks if the user has the mod role, set in the config json string - if (context.message.member.roles.cache.find(role => role.name == context.client.config.kick.modrole)) { - // Gets the first user pinged in the command - const user = context.message.mentions.users.first(); - - // If a user was pinged - if (user) { - // Gets the guild member object of the pinged user - const member = context.message.guild.member(user); - - // If the member object exists, i.e if the user is in the server - if (member) { - // Gets the part of the argument array which holds the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Joins the reason into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // If the bot client can kick the mentioned member - if (member.kickable) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setTitle("Member Kicked") - .setColor(embedColor) - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been kicked`); - - // Attemtp to kick the user, if successful send the embeds, if unsuccessful notify the chat and log the error - member.kick({ reason: reason }).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.kick.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { - errorEmbed(context, "An error has occurred"); - console.log(err); - }); - } else { // If the user isn't kickable - errorEmbed(context, "I am unable to kick this user"); - } - } - } else { // If the member object is invalid - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object is invalid - errorEmbed(context, "Please specify a valid user"); - } - } - } -} - -// Function to post an embed in case of an error -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = kick; diff --git a/src/commands/kick.ts b/src/commands/kick.ts new file mode 100644 index 0000000..1d9123f --- /dev/null +++ b/src/commands/kick.ts @@ -0,0 +1,61 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Kick extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1) + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.kickable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Kicked"); + logEmbed.AddUser("User", targetUser, true); + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been kicked`); + + await targetMember.kick(reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file From 0d3134bf4541e7e888e4007d72edc9c4adb80894 Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 29 Nov 2021 15:06:13 +0000 Subject: [PATCH 10/28] Migrate mute command --- .env.template | 2 + commands/mute.js | 98 ---------------------------------- src/commands/mute.ts | 70 ++++++++++++++++++++++++ src/constants/ErrorMessages.ts | 1 + src/helpers/PublicEmbed.ts | 5 ++ src/vylbot.ts | 1 + 6 files changed, 79 insertions(+), 98 deletions(-) delete mode 100644 commands/mute.js create mode 100644 src/commands/mute.ts diff --git a/.env.template b/.env.template index bfbe4a3..6320a30 100644 --- a/.env.template +++ b/.env.template @@ -20,6 +20,8 @@ FOLDERS_EVENTS=src/events COMMANDS_DISABLED= COMMANDS_DISABLED_MESSAGE=This command is disabled. +COMMANDS_MUTE_ROLE=Muted + EMBED_COLOUR=0x3050ba EMBED_COLOUR_ERROR=0xD52803 diff --git a/commands/mute.js b/commands/mute.js deleted file mode 100644 index 3b1fcd1..0000000 --- a/commands/mute.js +++ /dev/null @@ -1,98 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class mute extends command { - constructor() { - // Set the command's run method, description, category, and usage - super("mute"); - super.description = "Mutes the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set the required configs for the command - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - super.requiredConfigs = "muterole"; - } - - // The command's run method - mute(context) { - // Check if the user has the mod role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.mute.modrole)) { - // Get the user first pinged in the message - const user = context.message.mentions.users.first(); - - // If the user object exists - if (user) { - // Get the guild member object of the mentioned user - const member = context.message.guild.member(user); - - // If the member object exists, i.e. if the user is in the server - if (member) { - // Get the part of the arguments array which contains the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the reason into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // If the bot client can manage the user's roles - if (member.manageable) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setTitle("Member Muted") - .setColor(embedColor) - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into the channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been muted`) - .addField("Reason", reason || "*none*"); - - // Get the 'Muted' role - const mutedRole = context.message.guild.roles.cache.find(role => role.name == context.client.config.mute.muterole); - - // Attempt to mute the user, if successful send the embeds, if not log the error - member.roles.add(mutedRole, reason).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.mute.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { - errorEmbed(context, "An error occurred"); - console.log(err); - }); - } else { // If the bot can't manage the user - errorEmbed(context, "I am unable to mute this user"); - } - } - } else { // If the member object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } - } -} - -// Send an embed when an error occurs -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = mute; diff --git a/src/commands/mute.ts b/src/commands/mute.ts new file mode 100644 index 0000000..b1fe8bb --- /dev/null +++ b/src/commands/mute.ts @@ -0,0 +1,70 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Mute extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1); + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.manageable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Muted"); + logEmbed.AddUser("User", targetUser, true) + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been muted`); + publicEmbed.AddReason(reason); + + const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.COMMANDS_MUTE_ROLE); + + if (!mutedRole) { + const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); + embed.SendToCurrentChannel(); + return; + } + + await targetMember.roles.add(mutedRole, reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/constants/ErrorMessages.ts b/src/constants/ErrorMessages.ts index d3c1fe1..c2c9434 100644 --- a/src/constants/ErrorMessages.ts +++ b/src/constants/ErrorMessages.ts @@ -1,4 +1,5 @@ export default class ErrorMessages { public static readonly InsufficientBotPermissions = "Unable to do this action, am I missing permissions?"; public static readonly CantFindChannel = "Unable to find channel"; + public static readonly RoleNotFound = "Unable to find role"; } \ No newline at end of file diff --git a/src/helpers/PublicEmbed.ts b/src/helpers/PublicEmbed.ts index 790f27f..da278e5 100644 --- a/src/helpers/PublicEmbed.ts +++ b/src/helpers/PublicEmbed.ts @@ -14,6 +14,11 @@ export default class PublicEmbed extends MessageEmbed { this._context = context; } + // Detail methods + public AddReason(message: String) { + super.addField("Reason", message || "*none*"); + } + // Send methods public SendToCurrentChannel() { this._context.message.channel.send(this); diff --git a/src/vylbot.ts b/src/vylbot.ts index c9b0eee..ed42734 100644 --- a/src/vylbot.ts +++ b/src/vylbot.ts @@ -12,6 +12,7 @@ if (!process.env.ROLES_MODERATOR) throw "ROLES_MODERATOR is required in .env"; if (!process.env.CHANNELS_LOGS_MESSAGE) throw "CHANNELS_LOGS_MESSAGE is required in .env"; if (!process.env.CHANNELS_LOGS_MEMBER) throw "CHANNELS_LOGS_MEMBER is required in .env"; if (!process.env.CHANNELS_LOGS_MOD) throw "CHANNELS_LOGS_MOD is required in .env"; +if (!process.env.COMMANDS_MUTE_ROLE) throw "COMMANDS_MUTE_ROLE is required in .env"; const client = new CoreClient(); client.start(); \ No newline at end of file From e7169d960a8e15e24b26266af7515aa2d672e88f Mon Sep 17 00:00:00 2001 From: Ethan Lane Date: Mon, 29 Nov 2021 15:34:57 +0000 Subject: [PATCH 11/28] Migrate poll command --- commands/partner.js | 60 ----------------- commands/poll.js | 150 ------------------------------------------- src/commands/poll.ts | 56 ++++++++++++++++ 3 files changed, 56 insertions(+), 210 deletions(-) delete mode 100644 commands/partner.js delete mode 100644 commands/poll.js create mode 100644 src/commands/poll.ts diff --git a/commands/partner.js b/commands/partner.js deleted file mode 100644 index bf59622..0000000 --- a/commands/partner.js +++ /dev/null @@ -1,60 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const { existsSync, readFileSync } = require('fs'); - -// Command Variables -const embedColor = "0x3050ba"; - -// Command class -class partner extends command { - constructor() { - // Set the command's run method, description, and category - super("partner"); - super.description = "Generates the embeds for the partner from the partners.json file"; - super.category = "Admin"; - - // Require in the config the name of the admin role and the rules file name - super.requiredConfigs = "adminrole"; - super.requiredConfigs = "partnersfile"; - } - - // Run method - partner(context) { - if (context.message.member.roles.cache.find(role => role.name == context.client.config.partner.adminrole)) { - if (existsSync(context.client.config.partner.partnersfile)) { - const partnerJson = JSON.parse(readFileSync(context.client.config.partner.partnersfile)); - - for (const i in partnerJson) { - const serverName = partnerJson[i].name; - const serverInvite = partnerJson[i].invite; - const serverDescription = partnerJson[i].description; - const serverIcon = partnerJson[i].icon; - - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle(serverName) - .setDescription(serverDescription) - .setURL(serverInvite) - .setThumbnail(serverIcon); - - context.message.channel.send(embed); - } - } else { - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription('File does not exist'); - - context.message.channel.send(errorEmbed); - } - } else { - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription('You do not have permission to run this command'); - - context.message.channel.send(errorEmbed); - } - } -} - -module.exports = partner; \ No newline at end of file diff --git a/commands/poll.js b/commands/poll.js deleted file mode 100644 index 7afe92e..0000000 --- a/commands/poll.js +++ /dev/null @@ -1,150 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const emojiRegex = require('emoji-regex/RGI_Emoji'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class poll extends command { - constructor() { - // Set the command's run method, description, category, and example usage - super("poll"); - super.description = "Generates a poll with reaction numbers"; - super.category = "General"; - super.usage = ";<option 1>;<option 2>..."; - } - - // Run method - poll(context) { - // Get the command's arguments, and split them by a semicolon rather than a space - // This allows the variables to be able to use spaces in them - let args = context.arguments; - const argsJoined = args.join(' '); - args = argsJoined.split(';'); - - // If the argument has 3 or more arguments and less than 11 arguments - // This allows the title and 2-9 options - if (args.length >= 3 && args.length < 11) { - // Set the title to the first argument - const title = args[0]; - let optionString = ""; - - // Array used to get the numbers as their words - // arrayOfNumbers[n] = "n written in full words" - const arrayOfNumbers = [ - ':zero:', - ':one:', - ':two:', - ':three:', - ':four:', - ':five:', - ':six:', - ':seven:', - ':eight:', - ':nine:' - ]; - - // Array containing the numbers as their emoji - const reactionEmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]; - - // Loop through all the arguments after the title - // Add them to the optionString, with their index turned into a number emoji - // Example: :one: Option 1 - for (let i = 1; i < args.length; i++) { - // If the option contains an emoji, replace the emoji with it - const regex = emojiRegex(); - const match = regex.exec(args[i]); - - if (match) { - const emoji = match[0]; - reactionEmojis[i] = emoji; - arrayOfNumbers[i] = ''; - } - - optionString += `${arrayOfNumbers[i]} ${args[i]}\n`; - } - - // Create the embed with the title at the top of the description with the options below - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`**${title}**\n\n${optionString}`); - - // Send the embed and then react with the numbers for users to react with, - // the bot will determine how many to react with for the amount of options inputted - context.message.channel.send(embed).then(message => { - if (args.length == 2) { - message.react(reactionEmojis[1]); - } else if (args.length == 3) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])); - } else if (args.length == 4) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])); - } else if (args.length == 5) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])); - } else if (args.length == 6) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])); - } else if (args.length == 7) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])); - } else if (args.length == 8) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])) - .then(() => message.react(reactionEmojis[7])); - } else if (args.length == 9) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])) - .then(() => message.react(reactionEmojis[7])) - .then(() => message.react(reactionEmojis[8])); - } else if (args.length == 10) { - message.react(reactionEmojis[1]) - .then(() => message.react(reactionEmojis[2])) - .then(() => message.react(reactionEmojis[3])) - .then(() => message.react(reactionEmojis[4])) - .then(() => message.react(reactionEmojis[5])) - .then(() => message.react(reactionEmojis[6])) - .then(() => message.react(reactionEmojis[7])) - .then(() => message.react(reactionEmojis[8])) - .then(() => message.react(reactionEmojis[9])); - } - }).catch(console.error); - - // Delete the message - context.message.delete(); - } else if (args.length >= 11) { // If the user inputted more than 9 options - const errorEmbed = new MessageEmbed() - .setDescription("The poll command can only accept up to 9 options"); - - context.message.channel.send(errorEmbed); - } else { // If the user didn't give enough data - const errorEmbed = new MessageEmbed() - .setDescription("Please use the correct usage: <title>;<option 1>;<option 2>... (separate options with semicolons)"); - - context.message.channel.send(errorEmbed); - } - } -} - -module.exports = poll; diff --git a/src/commands/poll.ts b/src/commands/poll.ts new file mode 100644 index 0000000..3d53194 --- /dev/null +++ b/src/commands/poll.ts @@ -0,0 +1,56 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Poll extends Command { + constructor() { + super(); + + super._category = "General"; + } + + public override async execute(context: ICommandContext) { + const argsJoined = context.args.join(" "); + const argsSplit = argsJoined.split(";"); + + if (argsSplit.length < 3 || argsSplit.length > 10) { + const errorEmbed = new ErrorEmbed(context, "Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const title = argsSplit[0]; + + const arrayOfNumbers = [ + ':one:', + ':two:', + ':three:', + ':four:', + ':five:', + ':six:', + ':seven:', + ':eight:', + ':nine:' + ]; + + const reactionEmojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]; + + const description = arrayOfNumbers.splice(0, argsSplit.length - 1); + + description.forEach((value, index) => { + description[index] = `${value} ${argsSplit[index + 1]}`; + }); + + const embed = new PublicEmbed(context, title, description.join("\n")); + + const message = await context.message.channel.send(embed); + + description.forEach(async (value, index) => { + await message.react(reactionEmojis[index]); + }); + + if (context.message.deletable) { + await context.message.delete({ reason: "Poll command" }); + } + } +} \ No newline at end of file From 35f7210b6e5ddf408983f53356406b580c79b2d8 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 11:11:46 +0000 Subject: [PATCH 12/28] Migrate bunny command --- commands/bunny.js | 35 -------- package.json | 2 +- yarn.lock | 201 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 196 insertions(+), 42 deletions(-) delete mode 100644 commands/bunny.js diff --git a/commands/bunny.js b/commands/bunny.js deleted file mode 100644 index 97baa86..0000000 --- a/commands/bunny.js +++ /dev/null @@ -1,35 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const randomBunny = require('random-bunny'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class bunny extends command { - constructor() { - // Set run method, description, and category - super("bunny"); - super.description = "Gives you a random bunny"; - super.category = "Fun"; - } - - // Run method - bunny(context) { - // Get a random post from r/Rabbits - randomBunny('rabbits', 'hot', (image, title) => { - // Create an embed containing the random image - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle(title) - .setImage(image) - .setFooter('r/Rabbits'); - - // Send the embed - context.message.channel.send(embed); - }); - } -} - -module.exports = bunny; diff --git a/package.json b/package.json index 0db313d..a032aeb 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "dotenv": "^10.0.0", "emoji-regex": "^9.2.0", - "random-bunny": "^1.0.0", + "random-bunny": "^2.0.0", "vylbot-core": "^2.0.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 6a27750..011876d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,6 +78,18 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@sindresorhus/is@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.0.tgz#667bfc6186ae7c9e0b45a08960c551437176e1ca" + integrity sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw== + +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -98,11 +110,45 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== +"@types/cacheable-request@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" + integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + +"@types/http-cache-semantics@*": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" + integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== + +"@types/keyv@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41" + integrity sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "16.11.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.11.tgz#6ea7342dfb379ea1210835bada87b3c512120234" + integrity sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw== + "@types/node@^16.11.10": version "16.11.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.10.tgz#2e3ad0a680d96367103d3e670d41c2fed3da61ae" integrity sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA== +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -209,6 +255,24 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -231,6 +295,13 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -288,11 +359,23 @@ debug@^4.0.1, debug@^4.1.1: dependencies: ms "2.1.2" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -339,6 +422,13 @@ emoji-regex@^9.2.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -520,6 +610,13 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -553,6 +650,23 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" +got@^11.8.3: + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -563,6 +677,19 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -629,6 +756,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -644,6 +776,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +keyv@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.4.tgz#f040b236ea2b06ed15ed86fbef8407e1a1c8e376" + integrity sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg== + dependencies: + json-buffer "3.0.1" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -662,6 +801,11 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -686,6 +830,16 @@ mime-types@^2.1.12: dependencies: mime-db "1.51.0" +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -710,7 +864,12 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" -once@^1.3.0: +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -729,6 +888,11 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -761,18 +925,31 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -random-bunny@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/random-bunny/-/random-bunny-1.2.2.tgz#821882ba33b6be05e3a538c43e822e5e9896862d" - integrity sha512-m12WIb4uNeEA8eLk9vxK6U9x/+KlRwBqOoiuGku6C6kF6DcbyMTq0RaRUXF3fXxpRtZbpoU7dgttLoETTXXL/g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +random-bunny@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/random-bunny/-/random-bunny-2.0.0.tgz#f9950f53c7f5183a6dad26386db14f702d477cf7" + integrity sha512-xIDaPghs0nslKWNfxDLAGNtsUPUlmNagUmdNAHP7U4LSrGySbgSfAzCV8eECTeuny2csmf0SL5dBxEZdVvHgOA== dependencies: glob-parent "^6.0.0" - node-fetch "^2.6.1" + got "^11.8.3" regexpp@^3.1.0: version "3.2.0" @@ -784,11 +961,23 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" From 90ef4317cc6c44142de169d656645892623e0799 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 11:44:56 +0000 Subject: [PATCH 13/28] Update required roles checker --- .env.template | 3 ++- src/commands/mute.ts | 2 +- src/vylbot.ts | 24 +++++++++++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.env.template b/.env.template index 6320a30..a588213 100644 --- a/.env.template +++ b/.env.template @@ -20,12 +20,13 @@ FOLDERS_EVENTS=src/events COMMANDS_DISABLED= COMMANDS_DISABLED_MESSAGE=This command is disabled. -COMMANDS_MUTE_ROLE=Muted +COMMANDS_ROLE_ROLES=Notify,VotePings,ProjectUpdates EMBED_COLOUR=0x3050ba EMBED_COLOUR_ERROR=0xD52803 ROLES_MODERATOR=Moderator +ROLES_MUTED=Muted CHANNELS_LOGS_MESSAGE=message-logs CHANNELS_LOGS_MEMBER=member-logs diff --git a/src/commands/mute.ts b/src/commands/mute.ts index b1fe8bb..774e437 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -54,7 +54,7 @@ export default class Mute extends Command { const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been muted`); publicEmbed.AddReason(reason); - const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.COMMANDS_MUTE_ROLE); + const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED); if (!mutedRole) { const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); diff --git a/src/vylbot.ts b/src/vylbot.ts index ed42734..301c8f0 100644 --- a/src/vylbot.ts +++ b/src/vylbot.ts @@ -3,16 +3,22 @@ import * as dotenv from "dotenv"; dotenv.config(); -// Ensure required data is in dotenv -if (!process.env.EMBED_COLOUR) throw "EMBED_COLOUR is required in .env"; -if (!process.env.EMBED_COLOUR_ERROR) throw "EMBED_COLOUR_ERROR is required in .env"; +const requiredConfigs = [ + "EMBED_COLOUR", + "EMBED_COLOUR_ERROR", + "ROLES_MODERATOR", + "ROLES_MUTED", + "CHANNELS_LOGS_MESSAGE", + "CHANNELS_LOGS_MEMBER", + "CHANNELS_LOGS_MOD", + "COMMANDS_ROLE_ROLES" +]; -if (!process.env.ROLES_MODERATOR) throw "ROLES_MODERATOR is required in .env"; - -if (!process.env.CHANNELS_LOGS_MESSAGE) throw "CHANNELS_LOGS_MESSAGE is required in .env"; -if (!process.env.CHANNELS_LOGS_MEMBER) throw "CHANNELS_LOGS_MEMBER is required in .env"; -if (!process.env.CHANNELS_LOGS_MOD) throw "CHANNELS_LOGS_MOD is required in .env"; -if (!process.env.COMMANDS_MUTE_ROLE) throw "COMMANDS_MUTE_ROLE is required in .env"; +requiredConfigs.forEach(config => { + if (!process.env[config]) { + throw `${config} is required in .env`; + } +}); const client = new CoreClient(); client.start(); \ No newline at end of file From 07c7155027fb673c615bde8e327cff7a203d8068 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 11:45:02 +0000 Subject: [PATCH 14/28] Migrate role command --- commands/role.js | 105 ------------------------------------------- src/commands/role.ts | 69 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 105 deletions(-) delete mode 100644 commands/role.js create mode 100644 src/commands/role.ts diff --git a/commands/role.js b/commands/role.js deleted file mode 100644 index 733be83..0000000 --- a/commands/role.js +++ /dev/null @@ -1,105 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class role extends command { - constructor() { - // Set the command's run method, description, category, and example usage - super("role"); - super.description = "Toggles a role for the user to gain/remove"; - super.category = "General"; - super.usage = "[name]"; - - // Require in the config the 'assignable roles' array - super.requiredConfigs = "assignable"; - } - - // Run method - role(context) { - // Get the array containing the assignable roles - const roles = context.client.config.role.assignable; - let requestedRole = ""; - - // If the arguments specifys a specific role - if (context.arguments.length > 0) { - // Loop through all the assignable roles and check against the first parameter - // Save the role name if they match, i.e. the role can be assignable - for (let i = 0; i < roles.length; i++) { - if (roles[i].toLowerCase() == context.arguments[0].toLowerCase()) { - requestedRole = roles[i]; - } - } - - // If a matching assignable role was found - if (requestedRole != "") { - // Get the role object from the server with the role name - const role = context.message.guild.roles.cache.find(r => r.name == requestedRole); - - // If the user already has the role, remove the role from them and send an embed - // Otherwise, add the role and send an embed - if (context.message.member.roles.cache.find(r => r.name == requestedRole)) { - context.message.member.roles.remove(role).then(() => { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`Removed role: ${requestedRole}`); - - context.message.channel.send(embed); - }).catch(err => { - console.error(err); - - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription("An error occured. Please check logs"); - - context.message.channel.send(errorEmbed); - }); - } else { // If the user doesn't have the role - context.message.member.roles.add(role).then(() => { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`Gave role: ${requestedRole}`); - - context.message.channel.send(embed); - }).catch(err => { - console.error(err); - - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription("An error occured. Please check logs"); - - context.message.channel.send(errorEmbed); - }); - } - } else { // If the role can't be found, send an error embed - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription("This role does not exist, see assignable roles with the role command (no arguments)"); - - context.message.channel.send(embed); - } - } else { // If no role was specified, Send a list of the roles you can assign - // The start of the embed text - let rolesString = `Do ${context.client.config.prefix}role <role> to get the role!\n`; - - // Loop through all the roles, and add them to the embed text - for (let i = 0; i < roles.length; i++) { - rolesString += `${roles[i]}\n`; - } - - // Create an embed containing the text - const embed = new MessageEmbed() - .setTitle("Roles") - .setColor(embedColor) - .setDescription(rolesString); - - // Send the embed - context.message.channel.send(embed); - } - } -} - -module.exports = role; diff --git a/src/commands/role.ts b/src/commands/role.ts new file mode 100644 index 0000000..53c15d2 --- /dev/null +++ b/src/commands/role.ts @@ -0,0 +1,69 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; +import { Role as DiscordRole } from "discord.js"; + +export default class Role extends Command { + constructor() { + super(); + + super._category = "General"; + } + + public override execute(context: ICommandContext) { + const roles = process.env.COMMANDS_ROLE_ROLES!.split(','); + + if (context.args.length == 0) { + this.SendRolesList(context, roles); + } else { + this.ToggleRole(context, roles); + } + } + + private SendRolesList(context: ICommandContext, roles: String[]) { + const description = `Do ${process.env.BOT_PREFIX}role <role> to get the role!\n${roles.join('\n')}`; + + const embed = new PublicEmbed(context, "Roles", description); + embed.SendToCurrentChannel(); + } + + private ToggleRole(context: ICommandContext, roles: String[]) { + const requestedRole = context.args[0]; + + if (!roles.includes(requestedRole)) { + const errorEmbed = new ErrorEmbed(context, "This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const assignRole = context.message.guild?.roles.cache.find(x => x.name == requestedRole); + + if (!assignRole) { + const errorEmbed = new ErrorEmbed(context, "The current server doesn't have this role. Please contact the server's moderators"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const role = context.message.member?.roles.cache.find(x => x.name == requestedRole) + + if (!role) { + this.AddRole(context, assignRole); + } else { + this.RemoveRole(context, assignRole); + } + } + + private async AddRole(context: ICommandContext, role: DiscordRole) { + await context.message.member?.roles.add(role); + + const embed = new PublicEmbed(context, "", `Gave role: ${role.name}`); + embed.SendToCurrentChannel(); + } + + private async RemoveRole(context: ICommandContext, role: DiscordRole) { + await context.message.member?.roles.remove(role); + + const embed = new PublicEmbed(context, "", `Removed role: ${role.name}`); + embed.SendToCurrentChannel(); + } +} \ No newline at end of file From 4ff88d0694107baeeb3679ebc3ab3965b3aba286 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 12:45:25 +0000 Subject: [PATCH 15/28] Migrate unmute command --- commands/unmute.js | 98 ------------------------------------------ src/commands/unmute.ts | 70 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 98 deletions(-) delete mode 100644 commands/unmute.js create mode 100644 src/commands/unmute.ts diff --git a/commands/unmute.js b/commands/unmute.js deleted file mode 100644 index 24b7528..0000000 --- a/commands/unmute.js +++ /dev/null @@ -1,98 +0,0 @@ -// Required components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class unmute extends command { - constructor() { - // Set run method, description, category, usage - super("unmute"); - super.description = "Unmutes the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set required configs - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - super.requiredConfigs = "muterole"; - } - - // The command's run method - unmute(context) { - // Check if the user has the mod role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.mute.modrole)) { - // Get the user first pinged in the message - const user = context.message.mentions.users.first(); - - // If the user object exists - if (user) { - // Get the guild member object from the pinged user - const member = context.message.guild.member(user); - - // If the member object exists, i.e. if the user is in the server - if (member) { - // Get the part of the argument array which contains the reason - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the array into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // If the bot client can manage the user - if (member.manageable) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setColor(embedColor) - .setTitle("Member Unmuted") - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into the channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been unmuted`) - .addField("Reason", reason || "*none*"); - - // Get the muted role - const mutedRole = context.message.guild.roles.cache.find(role => role.name == context.client.config.unmute.muterole); - - // Attempt to remove the role from the user, and then send the embeds. If unsuccessful log the error - member.roles.remove(mutedRole, reason).then(() => { - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.unmute.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - }).catch(err => { - errorEmbed(context, "An error occurred"); - console.log(err); - }); - } else { // If the bot can't manage the user - errorEmbed(context, "I am unable to unmute this user"); - } - } - } else { // If the member object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } - } -} - -// Send an embed in case of an error -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = unmute; diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts new file mode 100644 index 0000000..07fb03a --- /dev/null +++ b/src/commands/unmute.ts @@ -0,0 +1,70 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Unmute extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override async execute(context: ICommandContext) { + const targetUser = context.message.mentions.users.first(); + + if (!targetUser) { + const embed = new ErrorEmbed(context, "User does not exist"); + embed.SendToCurrentChannel(); + return; + } + + const targetMember = context.message.guild?.member(targetUser); + + if (!targetMember) { + const embed = new ErrorEmbed(context, "User is not in this server"); + embed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1); + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + if (!targetMember.manageable) { + const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); + embed.SendToCurrentChannel(); + return; + } + + const logEmbed = new LogEmbed(context, "Member Unmuted"); + logEmbed.AddUser("User", targetUser, true) + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been unmuted`); + publicEmbed.AddReason(reason); + + const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED); + + if (!mutedRole) { + const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); + embed.SendToCurrentChannel(); + return; + } + + await targetMember.roles.remove(mutedRole, reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file From c62488aa6350b4ae379686acd2787bbf61c28e41 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 13:02:13 +0000 Subject: [PATCH 16/28] Migrate warn command --- commands/warn.js | 86 -------------------------------------------- src/commands/warn.ts | 53 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 86 deletions(-) delete mode 100644 commands/warn.js create mode 100644 src/commands/warn.ts diff --git a/commands/warn.js b/commands/warn.js deleted file mode 100644 index 15d951e..0000000 --- a/commands/warn.js +++ /dev/null @@ -1,86 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -const embedColor = "0x3050ba"; - -// Command Class -class warn extends command { - constructor() { - // Set the run method, description, category, and usage - super("warn"); - super.description = "Warns the mentioned user with an optional reason"; - super.category = "Moderation"; - super.usage = "<@user> [reason]"; - - // Set the required configs - super.requiredConfigs = "modrole"; - super.requiredConfigs = "logchannel"; - } - - // The command's run method - warn(context) { - // If the user has the mod role - if (context.message.member.roles.cache.find(role => role.name == context.client.config.warn.modrole)) { - // Get the user first pinged in the message - const user = context.message.mentions.users.first(); - - // If the user object exists - if (user) { - // Get the guild member object from the user - const member = context.message.guild.member(user); - - // If the member object exists. i.e. if the user is in the server - if (member) { - // Get the part of the argument array which the reason is in - const reasonArgs = context.arguments; - reasonArgs.splice(0, 1); - - // Join the array into a string - const reason = reasonArgs.join(" "); - - // If the server is available - if (context.message.guild.available) { - // The embed to go into the bot log - const embedLog = new MessageEmbed() - .setColor(embedColor) - .setTitle("Member Warned") - .addField("User", `${user} \`${user.tag}\``, true) - .addField("Moderator", `${context.message.author} \`${context.message.author.tag}\``, true) - .addField("Reason", reason || "*none*") - .setThumbnail(user.displayAvatarURL); - - // The embed to go into the channel the command was sent in - const embedPublic = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${user} has been warned`) - .addField("Reason", reason || "*none*"); - - // Send the embeds - context.message.guild.channels.cache.find(channel => channel.name == context.client.config.warn.logchannel).send(embedLog); - context.message.channel.send(embedPublic); - - context.message.delete(); - } - } else { // If the member objest doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user object doesn't exist - errorEmbed(context, "Please specify a valid user"); - } - } else { // If the user isn't mod - errorEmbed(context, "You do not have permission to run this command"); - } - } -} - -// Send an embed in case of an error -function errorEmbed(context, message) { - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription(message); - - context.message.channel.send(embed); -} - -module.exports = warn; diff --git a/src/commands/warn.ts b/src/commands/warn.ts new file mode 100644 index 0000000..fdd186f --- /dev/null +++ b/src/commands/warn.ts @@ -0,0 +1,53 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import LogEmbed from "../helpers/LogEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Warn extends Command { + constructor() { + super(); + + super._category = "Moderation"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override execute(context: ICommandContext) { + const user = context.message.mentions.users.first(); + + if (!user) { + const errorEmbed = new ErrorEmbed(context, "Please specify a valid user"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const member = context.message.guild?.member(user); + + if (!member) { + const errorEmbed = new ErrorEmbed(context, "Please specify a valid user"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const reasonArgs = context.args; + reasonArgs.splice(0, 1); + + const reason = reasonArgs.join(" "); + + if (!context.message.guild?.available) { + return; + } + + const logEmbed = new LogEmbed(context, "Member Warned"); + logEmbed.AddUser("User", user, true); + logEmbed.AddUser("Moderator", context.message.author); + logEmbed.AddReason(reason); + + const publicEmbed = new PublicEmbed(context, "", `${user} has been warned`); + publicEmbed.AddReason(reason); + + logEmbed.SendToModLogsChannel(); + publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file From acedbffdad6db589f1609c8f910e86f46f8c4a2b Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 13:15:08 +0000 Subject: [PATCH 17/28] Migrate eval command --- .env.template | 1 + commands/eval.js | 25 ------------------------- src/commands/eval.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 25 deletions(-) delete mode 100644 commands/eval.js create mode 100644 src/commands/eval.ts diff --git a/.env.template b/.env.template index a588213..4d566fd 100644 --- a/.env.template +++ b/.env.template @@ -11,6 +11,7 @@ BOT_PREFIX=v! BOT_VER=3.0 BOT_AUTHOR=Vylpes BOT_DATE=28 Nov 2021 +BOT_OWNERID=147392775707426816 CORE_VER=2.0.2 diff --git a/commands/eval.js b/commands/eval.js deleted file mode 100644 index bdd46e1..0000000 --- a/commands/eval.js +++ /dev/null @@ -1,25 +0,0 @@ -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -class evaluate extends command { - constructor() { - super("evaluate"); - super.description = "Evaluates an expression"; - super.category = "Administration"; - super.requiredConfigs = "ownerid"; - } - - evaluate(context) { - if (context.message.author.id == context.client.config.eval.ownerid) { - const result = eval(context.arguments.join(" ")); - - const embed = new MessageEmbed() - .setDescription(result) - .setColor(0x3050ba); - - context.message.channel.send(embed); - } - } -} - -module.exports = evaluate; diff --git a/src/commands/eval.ts b/src/commands/eval.ts new file mode 100644 index 0000000..0c8154f --- /dev/null +++ b/src/commands/eval.ts @@ -0,0 +1,32 @@ +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +export default class Evaluate extends Command { + constructor() { + super(); + + super._category = "Owner"; + } + + public override execute(context: ICommandContext) { + if (context.message.author.id != process.env.BOT_OWNERID) { + return; + } + + const stmt = context.args; + + console.log(`Eval Statement: ${stmt.join(" ")}`); + + try { + const result = eval(stmt.join(" ")); + + const embed = new PublicEmbed(context, "", result); + embed.SendToCurrentChannel(); + } + catch (err: any) { + const errorEmbed = new ErrorEmbed(context, err); + errorEmbed.SendToCurrentChannel(); + } + } +} \ No newline at end of file From 6c903077541f9eb2cc00f68daf47c3740f72d857 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 14:09:05 +0000 Subject: [PATCH 18/28] Migrate help command --- commands/help.js | 150 ------------------------------------- src/commands/help.ts | 118 +++++++++++++++++++++++++++++ src/helpers/StringTools.ts | 15 ++++ 3 files changed, 133 insertions(+), 150 deletions(-) delete mode 100644 commands/help.js create mode 100644 src/commands/help.ts create mode 100644 src/helpers/StringTools.ts diff --git a/commands/help.js b/commands/help.js deleted file mode 100644 index 71d7946..0000000 --- a/commands/help.js +++ /dev/null @@ -1,150 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const { readdirSync } = require('fs'); - -const embedColor = "0x3050ba"; - -// Command Class -class help extends command { - constructor() { - // Set the execute method, description, category, and example usage - super("help"); - super.description = "Gives a list of commands available in the bot"; - super.category = "General"; - super.usage = "[command]"; - } - - // Execute method - help(context) { - // Get the list of command folders the bot has been setup to check - const commandFolders = context.client.config.commands; - - // Empty arrays for commands - // allCommands: Will contain objects of all commands with their related info - // categories: Will contain strings of all the categories the commands are set to, unique - const allCommands = []; - const categories = []; - - // Loop through all the command folders set - // i = folder index - for (let i = 0; i < commandFolders.length; i++) { - // The current folder the bot is looking through - const folder = commandFolders[i]; - - // Read the directory of the current folder - const contents = readdirSync(`${process.cwd()}/${folder}`); - - // Loop through the contents of the folder - // j = file index in folder i - for (let j = 0; j < contents.length; j++) { - // Get command in the current folder to read - const file = require(`${process.cwd()}/${folder}/${contents[j]}`); - - // Initialise the command - const obj = new file(); - - // Data object containing the command information - const data = { - "name": contents[j].replace(".js", ""), - "description": obj.description, - "category": obj.category, - "usage": obj.usage, - "roles": obj.roles - }; - - // Push the command data to the allCommands Array - allCommands.push(data); - } - } - - // Loop through all the commands discovered by the previous loop - for (let i = 0; i < allCommands.length; i++) { - // Get the current command category name, otherwise "none" - const category = allCommands[i].category || "none"; - - // If the command isn't already set, set it. - // This will then make the categories array be an array of all categories which have been used but only one of each. - if (!categories.includes(category)) categories.push(category); - } - - // If an command name has been passed as an argument - // If so, send information about that command - // If not, send the help embed of all commands - if (context.arguments[0]) { - sendCommand(context, allCommands, context.arguments[0]); - } else { - sendAll(context, categories, allCommands); - } - } -} - -// Send embed of all commands -// context: The command context json string -// categories: The array of categories found -// allCommands: The array of the commands found -function sendAll(context, categories, allCommands) { - // Embed to be sent - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle("Commands"); - - // Loop through each command - for (let i = 0; i < categories.length; i++) { - // The category name of the current one to check - const category = categories[i]; - - // Empty Array for the next loop to filter out the current category - const commandsFilter = []; - - // Loop through allCommands - // If the command is set to the current category being checked, add it to the filter array - for (let j = 0; j < allCommands.length; j++) { - if (allCommands[j].category == category) commandsFilter.push(`\`${allCommands[j].name}\``); - } - - // Add a field to the embed which contains the category name and all the commands in that category - embed.addField(category, commandsFilter.join(", ")); - } - - // Send the embed - context.message.channel.send(embed); -} - -// Send information about a specific command -// context: The command context json string -// allCommands: The array of categories found -// name: The command name to check -function sendCommand(context, allCommands, name) { - let command = {}; - - // Loop through all commands, if the command name is the same as the one we're looking for, select it - for (let i = 0; i < allCommands.length; i++) { - if (allCommands[i].name == name) command = allCommands[i]; - } - - // If a matching command has been found - if (command.name) { - // Create an embed containing the related information of the command - // The title is the command name but sets the first letter to be capitalised - // If a set of information isn't set, set it to say "none" - const embed = new MessageEmbed() - .setColor(embedColor) - .setTitle(command.name[0].toUpperCase() + command.name.substring(1)) - .setDescription(command.description || "*none*") - .addField("Category", command.category || "*none*", true) - .addField("Usage", command.usage || "*none*", true) - .addField("Required Roles", command.roles.join(", ") || "*none*"); - - // Send the embed - context.message.channel.send(embed); - } else { // If no command has been found, then send an embed which says this - const embed = new MessageEmbed() - .setColor(embedColor) - .setDescription("Command does not exist"); - - context.message.channel.send(embed); - } -} - -module.exports = help; diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..dc56258 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,118 @@ +import { existsSync, readdirSync } from "fs"; +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; +import StringTools from "../helpers/StringTools"; + +interface ICommandData { + Exists: boolean; + Name?: string; + Category?: string; + Roles?: string[]; +} + +export default class Help extends Command { + constructor() { + super(); + + super._category = "General"; + } + + public override execute(context: ICommandContext) { + if (context.args.length == 0) { + this.SendAll(context); + } else { + this.SendSingle(context); + } + } + + private SendAll(context: ICommandContext) { + const allCommands = this.GetAllCommandData(); + const cateogries = this.DetermineCategories(allCommands); + + const embed = new PublicEmbed(context, "Commands", ""); + + cateogries.forEach(category => { + let filtered = allCommands.filter(x => x.Category == category); + + embed.addField(StringTools.Capitalise(category), filtered.flatMap(x => x.Name).join(", ")); + }); + + embed.SendToCurrentChannel(); + } + + private SendSingle(context: ICommandContext) { + const command = this.GetCommandData(context.args[0]); + + if (!command.Exists) { + const errorEmbed = new ErrorEmbed(context, "Command does not exist"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const embed = new PublicEmbed(context, StringTools.Capitalise(command.Name!), ""); + embed.addField("Category", StringTools.Capitalise(command.Category!)); + embed.addField("Required Roles", StringTools.Capitalise(command.Roles!.join(", ")) || "*none*"); + + embed.SendToCurrentChannel(); + } + + private GetAllCommandData(): ICommandData[] { + const result: ICommandData[] = []; + + const folder = process.env.FOLDERS_COMMANDS!; + + const contents = readdirSync(`${process.cwd()}/${folder}`); + + contents.forEach(name => { + const file = require(`${process.cwd()}/${folder}/${name}`).default; + const command = new file() as Command; + + const data: ICommandData = { + Exists: true, + Name: name.replace(".ts", ""), + Category: command._category || "none", + Roles: command._roles, + }; + + result.push(data); + }); + + return result; + } + + private GetCommandData(name: string): ICommandData { + const folder = process.env.FOLDERS_COMMANDS!; + const path = `${process.cwd()}/${folder}/${name}.ts`; + + if (!existsSync(path)) { + return { + Exists: false + }; + } + + const file = require(path).default; + const command = new file() as Command; + + const data: ICommandData = { + Exists: true, + Name: name, + Category: command._category || "none", + Roles: command._roles + }; + + return data; + } + + private DetermineCategories(commands: ICommandData[]): string[] { + const result: string[] = []; + + commands.forEach(cmd => { + if (!result.includes(cmd.Category!)) { + result.push(cmd.Category!); + } + }); + + return result; + } +} \ No newline at end of file diff --git a/src/helpers/StringTools.ts b/src/helpers/StringTools.ts new file mode 100644 index 0000000..b42eb90 --- /dev/null +++ b/src/helpers/StringTools.ts @@ -0,0 +1,15 @@ +export default class StringTools { + public static Capitalise(str: string): string { + const words = str.split(" "); + let result: string[] = []; + + words.forEach(word => { + const firstLetter = word.substring(0, 1).toUpperCase(); + const rest = word.substring(1); + + result.push(firstLetter + rest); + }); + + return result.join(" "); + } +} \ No newline at end of file From 24818bcb4494893ed5ddfc25abab03e6a0ebcbad Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 14:36:24 +0000 Subject: [PATCH 19/28] Migrate rules command --- .env.template | 2 ++ commands/rules.js | 70 --------------------------------------- data/partner/partner.json | 14 -------- src/commands/rules.ts | 46 +++++++++++++++++++++++++ src/vylbot.ts | 3 +- 5 files changed, 50 insertions(+), 85 deletions(-) delete mode 100644 commands/rules.js delete mode 100644 data/partner/partner.json create mode 100644 src/commands/rules.ts diff --git a/.env.template b/.env.template index 4d566fd..631f40c 100644 --- a/.env.template +++ b/.env.template @@ -23,6 +23,8 @@ COMMANDS_DISABLED_MESSAGE=This command is disabled. COMMANDS_ROLE_ROLES=Notify,VotePings,ProjectUpdates +COMMANDS_RULES_FILE=data/rules/rules.json + EMBED_COLOUR=0x3050ba EMBED_COLOUR_ERROR=0xD52803 diff --git a/commands/rules.js b/commands/rules.js deleted file mode 100644 index 266e504..0000000 --- a/commands/rules.js +++ /dev/null @@ -1,70 +0,0 @@ -// Required Components -const { command } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); -const { existsSync, readFileSync } = require('fs'); - -// Command variables -const embedColor = "0x3050ba"; - -// Command class -class rules extends command { - constructor() { - // Set the command's run method, description, and category - super("rules"); - super.description = "Generates the rules embeds from the rules.txt file"; - super.category = "Admin"; - - // Require in the config the name of the admin role and the rules file name - super.requiredConfigs = "adminrole"; - super.requiredConfigs = "rulesfile"; - } - - // Run method - rules(context) { - // If the user is an Admin (has the admin role) - if (context.message.member.roles.cache.find(role => role.name == context.client.config.rules.adminrole)) { - // If the rulesfile exists - if (existsSync(context.client.config.rules.rulesfile)) { - const rulesJson = readFileSync(context.client.config.rules.rulesfile); - const rules = JSON.parse(rulesJson); - - for (let i = 0; i < rules.length; i++) { - const rule = rules[i]; - const embed = new MessageEmbed(); - - embed.setColor(embedColor); - - if (rule.image) embed.setImage(rule.image); - if (rule.title) embed.setTitle(rule.title); - if (rule.footer) embed.setFooter(rule.footer); - if (rule.description) { - let description = ""; - - for (let j = 0; j < rule.description.length; j++) { - const line = rule.description[j]; - description += `${line}\n`; - } - - embed.setDescription(description); - } - - context.message.channel.send(embed); - } - } else { // If the rules file doesn't exist - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription(`${context.client.config.rules.rulesfile} doesn't exist`); - - context.message.channel.send(errorEmbed); - } - } else { // If the user doesn't have the Admin role - const errorEmbed = new MessageEmbed() - .setColor(embedColor) - .setDescription("You do not have permission to run this command"); - - context.message.channel.send(errorEmbed); - } - } -} - -module.exports = rules; diff --git a/data/partner/partner.json b/data/partner/partner.json deleted file mode 100644 index 73c3d34..0000000 --- a/data/partner/partner.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "name": "Cuzethstan", - "description": "Cuzeth Server. Yes.", - "invite": "http://discord.gg/uhEFNw7", - "icon": "https://cdn.discordapp.com/icons/720177983016665251/a_e4250e57b26559c6609dfe562774ee27.gif" - }, - { - "name": "Boblin", - "description": "Official server of the... Boblin?\n- Multiple Topics\n- Lots of Very Active Members", - "invite": "https://discord.gg/Td4uzVu", - "icon": "https://cdn.discordapp.com/attachments/464708407010787328/487824441846267907/image0.png" - } -] \ No newline at end of file diff --git a/src/commands/rules.ts b/src/commands/rules.ts new file mode 100644 index 0000000..9b9af9f --- /dev/null +++ b/src/commands/rules.ts @@ -0,0 +1,46 @@ +import { existsSync, readFileSync } from "fs"; +import { Command, ICommandContext } from "vylbot-core"; +import ErrorEmbed from "../helpers/ErrorEmbed"; +import PublicEmbed from "../helpers/PublicEmbed"; + +interface IRules { + title?: string; + description?: string[]; + image?: string; + footer?: string; +} + +export default class Rules extends Command { + constructor() { + super(); + + super._category = "Admin"; + super._roles = [ + process.env.ROLES_MODERATOR! + ]; + } + + public override execute(context: ICommandContext) { + if (!existsSync(process.env.COMMANDS_RULES_FILE!)) { + const errorEmbed = new ErrorEmbed(context, "Rules file doesn't exist"); + errorEmbed.SendToCurrentChannel(); + return; + } + + const rulesFile = readFileSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE}`).toString(); + const rules = JSON.parse(rulesFile) as IRules[]; + + const embeds: PublicEmbed[] = []; + + rules.forEach(rule => { + const embed = new PublicEmbed(context, rule.title || "", rule.description?.join("\n") || ""); + + embed.setImage(rule.image || ""); + embed.setFooter(rule.footer || ""); + + embeds.push(embed); + }); + + embeds.forEach(x => x.SendToCurrentChannel()); + } +} \ No newline at end of file diff --git a/src/vylbot.ts b/src/vylbot.ts index 301c8f0..80f7a25 100644 --- a/src/vylbot.ts +++ b/src/vylbot.ts @@ -11,7 +11,8 @@ const requiredConfigs = [ "CHANNELS_LOGS_MESSAGE", "CHANNELS_LOGS_MEMBER", "CHANNELS_LOGS_MOD", - "COMMANDS_ROLE_ROLES" + "COMMANDS_ROLE_ROLES", + "COMMANDS_RULES_FILE" ]; requiredConfigs.forEach(config => { From 44571d735a456c4e912dabcfdf653dd17d643959 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Thu, 2 Dec 2021 15:38:38 +0000 Subject: [PATCH 20/28] Migrate events to typescript --- events/guildMemberAdd.js | 32 ------------ events/guildMemberRemove.js | 32 ------------ events/guildMemberUpdate.js | 41 --------------- events/messageDelete.js | 32 ------------ package.json | 2 +- src/events/MemberEvents.ts | 36 +++++++++++++ src/events/MemberEvents/GuildMemberUpdate.ts | 25 +++++++++ src/events/MessageEvents.ts | 35 +++++++++++++ src/helpers/EventEmbed.ts | 54 ++++++++++++++++++++ yarn.lock | 8 +-- 10 files changed, 155 insertions(+), 142 deletions(-) delete mode 100644 events/guildMemberAdd.js delete mode 100644 events/guildMemberRemove.js delete mode 100644 events/guildMemberUpdate.js delete mode 100644 events/messageDelete.js create mode 100644 src/events/MemberEvents.ts create mode 100644 src/events/MemberEvents/GuildMemberUpdate.ts create mode 100644 src/events/MessageEvents.ts create mode 100644 src/helpers/EventEmbed.ts diff --git a/events/guildMemberAdd.js b/events/guildMemberAdd.js deleted file mode 100644 index 7b57581..0000000 --- a/events/guildMemberAdd.js +++ /dev/null @@ -1,32 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "member-logs"; - -// Event class -class guildmemberadd extends event { - constructor() { - // Set the event's run method - super("guildmemberadd"); - } - - // Run method - guildmemberadd(member) { - // Create an embed with the user who joined's information - const embed = new MessageEmbed() - .setTitle("Member Joined") - .setColor(embedColor) - .addField("User", `${member} \`${member.user.tag}\``) - .addField("Created", `${member.user.createdAt}`) - .setFooter(`User ID: ${member.user.id}`) - .setThumbnail(member.user.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the mod's log channel - member.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = guildmemberadd; diff --git a/events/guildMemberRemove.js b/events/guildMemberRemove.js deleted file mode 100644 index ed85ef2..0000000 --- a/events/guildMemberRemove.js +++ /dev/null @@ -1,32 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "member-logs"; - -// Event class -class guildmemberremove extends event { - constructor() { - // Set the event's run method - super("guildmemberremove"); - } - - // Run method - guildmemberremove(member) { - // Create an embed with the user's information - const embed = new MessageEmbed() - .setTitle("Member Left") - .setColor(embedColor) - .addField("User", `${member} \`${member.user.tag}\``) - .addField("Joined", `${member.joinedAt}`) - .setFooter(`User ID: ${member.user.id}`) - .setThumbnail(member.user.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the log channel - member.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = guildmemberremove; diff --git a/events/guildMemberUpdate.js b/events/guildMemberUpdate.js deleted file mode 100644 index 806903c..0000000 --- a/events/guildMemberUpdate.js +++ /dev/null @@ -1,41 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "member-logs"; - -// Event class -class guildmemberupdate extends event { - constructor() { - // Set the event's run method - super("guildmemberupdate"); - } - - // Run method - guildmemberupdate(oldMember, newMember) { - // If the user's nickname was changed - if (oldMember.nickname != newMember.nickname) { - // Get the user's name with tag, their old nickname and their new nickname - // If they didn't have a nickname or they removed it, set it to "none" in italics - const oldNickname = oldMember.nickname || "*none*"; - const newNickname = newMember.nickname || "*none*"; - - // Create the embed with the user's information - const embed = new MessageEmbed() - .setTitle("Nickname Changed") - .setColor(embedColor) - .addField("User", `${newMember} \`${newMember.user.tag}\``) - .addField("Before", oldNickname, true) - .addField("After", newNickname, true) - .setFooter(`User ID: ${newMember.user.id}`) - .setThumbnail(newMember.user.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the log channel - newMember.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } - } -} - -module.exports = guildmemberupdate; diff --git a/events/messageDelete.js b/events/messageDelete.js deleted file mode 100644 index 19fea4a..0000000 --- a/events/messageDelete.js +++ /dev/null @@ -1,32 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "message-logs"; - -// Event class -class messagedelete extends event { - constructor() { - // The event's run method - super("messagedelete"); - } - - // Run method - messagedelete(message) { - // Create an embed with the message's information - const embed = new MessageEmbed() - .setTitle("Message Deleted") - .setColor(embedColor) - .addField("User", `${message.author} \`${message.author.tag}\``) - .addField("Channel", message.channel) - .addField("Content", `\`\`\`${message.content || "*none*"}\`\`\``) - .setThumbnail(message.author.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed in the logging channel - message.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = messagedelete; diff --git a/package.json b/package.json index a032aeb..6874877 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dotenv": "^10.0.0", "emoji-regex": "^9.2.0", "random-bunny": "^2.0.0", - "vylbot-core": "^2.0.2" + "vylbot-core": "^2.0.3" }, "devDependencies": { "@types/node": "^16.11.10", diff --git a/src/events/MemberEvents.ts b/src/events/MemberEvents.ts new file mode 100644 index 0000000..696fa11 --- /dev/null +++ b/src/events/MemberEvents.ts @@ -0,0 +1,36 @@ +import { Event } from "vylbot-core"; +import { GuildMember } from "discord.js"; +import EventEmbed from "../helpers/EventEmbed"; +import GuildMemberUpdate from "./MemberEvents/GuildMemberUpdate"; + +export default class MemberEvents extends Event { + constructor() { + super(); + } + + public override guildMemberAdd(member: GuildMember) { + const embed = new EventEmbed(member.guild, "Member Joined"); + embed.AddUser("User", member.user, true); + embed.addField("Created", member.user.createdAt); + embed.setFooter(`Id: ${member.user.id}`); + + embed.SendToMemberLogsChannel(); + } + + public override guildMemberRemove(member: GuildMember) { + const embed = new EventEmbed(member.guild, "Member Left"); + embed.AddUser("User", member.user, true); + embed.addField("Joined", member.joinedAt); + embed.setFooter(`Id: ${member.user.id}`); + + embed.SendToMemberLogsChannel(); + } + + public override guildMemberUpdate(oldMember: GuildMember, newMember: GuildMember) { + const handler = new GuildMemberUpdate(oldMember, newMember); + + if (oldMember.nickname != newMember.nickname) { // Nickname change + handler.NicknameChanged(); + } + } +} \ No newline at end of file diff --git a/src/events/MemberEvents/GuildMemberUpdate.ts b/src/events/MemberEvents/GuildMemberUpdate.ts new file mode 100644 index 0000000..ef2c530 --- /dev/null +++ b/src/events/MemberEvents/GuildMemberUpdate.ts @@ -0,0 +1,25 @@ +import { GuildMember } from "discord.js"; +import EventEmbed from "../../helpers/EventEmbed"; + +export default class GuildMemberUpdate { + private _oldMember: GuildMember; + private _newMember: GuildMember; + + constructor(oldMember: GuildMember, newMember: GuildMember) { + this._oldMember = oldMember; + this._newMember = newMember; + } + + public NicknameChanged() { + const oldNickname = this._oldMember.nickname || "*none*"; + const newNickname = this._newMember.nickname || "*none*"; + + const embed = new EventEmbed(this._newMember.guild, "Nickname Changed"); + embed.AddUser("User", this._newMember.user, true); + embed.addField("Before", oldNickname, true); + embed.addField("After", newNickname, true); + embed.setFooter(`Id: ${this._newMember.user.id}`); + + embed.SendToMemberLogsChannel(); + } +} \ No newline at end of file diff --git a/src/events/MessageEvents.ts b/src/events/MessageEvents.ts new file mode 100644 index 0000000..0d449ec --- /dev/null +++ b/src/events/MessageEvents.ts @@ -0,0 +1,35 @@ +import { Event } from "vylbot-core"; +import { Message } from "discord.js"; +import EventEmbed from "../helpers/EventEmbed"; + +export default class MessageEvents extends Event { + constructor() { + super(); + } + + public override messageDelete(message: Message) { + if (!message.guild) return; + + const embed = new EventEmbed(message.guild, "Message Deleted"); + embed.AddUser("User", message.author, true); + embed.addField("Channel", message.channel, true); + embed.addField("Content", `\`\`\`${message.content || "*none*"}\`\`\``); + embed.addField("Attachments", `\`\`\`${message.attachments.map(x => x.url).join("\n")}`); + + embed.SendToMessageLogsChannel(); + } + + public override messageUpdate(oldMessage: Message, newMessage: Message) { + if (!newMessage.guild) return; + if (newMessage.author.bot) return; + if (oldMessage.content == newMessage.content) return; + + const embed = new EventEmbed(newMessage.guild, "Message Edited"); + embed.AddUser("User", newMessage.author, true); + embed.addField("Channel", newMessage.channel, true); + embed.addField("Before", `\`\`\`${oldMessage.content || "*none*"}\`\`\``); + embed.addField("After", `\`\`\`${newMessage.content || "*none*"}\`\`\``); + + embed.SendToMessageLogsChannel(); + } +} \ No newline at end of file diff --git a/src/helpers/EventEmbed.ts b/src/helpers/EventEmbed.ts new file mode 100644 index 0000000..25141d8 --- /dev/null +++ b/src/helpers/EventEmbed.ts @@ -0,0 +1,54 @@ +import { MessageEmbed, TextChannel, User, Guild } from "discord.js"; +import ErrorMessages from "../constants/ErrorMessages"; +import ErrorEmbed from "./ErrorEmbed"; + +export default class EventEmbed extends MessageEmbed { + private _guild: Guild; + + constructor(guild: Guild, title: string) { + super(); + + super.setColor(process.env.EMBED_COLOUR!); + super.setTitle(title); + + this._guild = guild; + } + + // Detail methods + public AddUser(title: string, user: User, setThumbnail: boolean = false) { + super.addField(title, `${user} \`${user.tag}\``, true); + + if (setThumbnail) { + super.setThumbnail(user.displayAvatarURL()); + } + } + + public AddReason(message: String) { + super.addField("Reason", message || "*none*"); + } + + // Send methods + public SendToChannel(name: string) { + const channel = this._guild.channels.cache + .find(channel => channel.name == name) as TextChannel; + + if (!channel) { + console.error(`Unable to find channel ${name}`); + return; + } + + channel.send(this); + } + + public SendToMessageLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MESSAGE!) + } + + public SendToMemberLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MEMBER!) + } + + public SendToModLogsChannel() { + this.SendToChannel(process.env.CHANNELS_LOGS_MOD!) + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 011876d..1b8040a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1131,10 +1131,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vylbot-core@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-2.0.2.tgz#8f83b836b423e9f0812abb86368672716dc1fa75" - integrity sha512-Og+cdPG+ExjzjhqVDb1hac7pm6KfLvT85NMBoL7hfbSPsg11wFwLKI6toqVnbvbqMKzKnOpIvijo4jx+fdUDsA== +vylbot-core@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-2.0.3.tgz#e7d844bc54e72b14f06f14b63866854e3b846d55" + integrity sha512-RxABRcwhVIVZcIssVP8yosLlUXzcdrNLiYJzcvkAIz4KeHfoARcxjsz2vG2x9GIzeFpfn/hjrPvjFC5lN6zclw== dependencies: discord.js "^12.3.1" dotenv "^10.0.0" From ba51cbb28cb355acc27ec1021eda4a5d9d42d87a Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Sat, 4 Dec 2021 15:42:58 +0000 Subject: [PATCH 21/28] Update about command to use the PublicEmbed class --- src/commands/about.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/commands/about.ts b/src/commands/about.ts index f6b30e3..f30e490 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -1,5 +1,5 @@ import { Command, ICommandContext } from "vylbot-core"; -import { MessageEmbed } from "discord.js"; +import PublicEmbed from "../helpers/PublicEmbed"; export default class About extends Command { constructor() { @@ -8,15 +8,12 @@ export default class About extends Command { } public override execute(context: ICommandContext) { - const embed = new MessageEmbed() - .setTitle("About") - .setColor(process.env.EMBED_COLOUR!) - .setDescription("About the bot") + const embed = new PublicEmbed(context, "About", "") .addField("Version", process.env.BOT_VER) .addField("VylBot Core", process.env.CORE_VER) .addField("Author", process.env.BOT_AUTHOR) .addField("Date", process.env.BOT_DATE); - context.message.channel.send(embed); + embed.SendToCurrentChannel(); } } \ No newline at end of file From 68b9ed34e44d2c6ceadf3e41bee04aed7dac3853 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Sat, 4 Dec 2021 15:46:47 +0000 Subject: [PATCH 22/28] Update ErrorMessage to ChannelNotFound --- src/constants/ErrorMessages.ts | 2 +- src/helpers/LogEmbed.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants/ErrorMessages.ts b/src/constants/ErrorMessages.ts index c2c9434..a397dcf 100644 --- a/src/constants/ErrorMessages.ts +++ b/src/constants/ErrorMessages.ts @@ -1,5 +1,5 @@ export default class ErrorMessages { public static readonly InsufficientBotPermissions = "Unable to do this action, am I missing permissions?"; - public static readonly CantFindChannel = "Unable to find channel"; + public static readonly ChannelNotFound = "Unable to find channel"; public static readonly RoleNotFound = "Unable to find role"; } \ No newline at end of file diff --git a/src/helpers/LogEmbed.ts b/src/helpers/LogEmbed.ts index 4eff943..c96acdc 100644 --- a/src/helpers/LogEmbed.ts +++ b/src/helpers/LogEmbed.ts @@ -38,7 +38,7 @@ export default class LogEmbed extends MessageEmbed { .find(channel => channel.name == name) as TextChannel; if (!channel) { - const errorEmbed = new ErrorEmbed(this._context, ErrorMessages.CantFindChannel); + const errorEmbed = new ErrorEmbed(this._context, ErrorMessages.ChannelNotFound); errorEmbed.SendToCurrentChannel(); return; } From 783c3a013d9299f5505d0af464c6aad82ceca0d9 Mon Sep 17 00:00:00 2001 From: Ethan Lane <ethan@vylpes.com> Date: Sat, 4 Dec 2021 16:47:43 +0000 Subject: [PATCH 23/28] Update messageDelete event to ignore bots --- events/messageUpdate.js | 37 ------------------------------------- src/events/MessageEvents.ts | 1 + 2 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 events/messageUpdate.js diff --git a/events/messageUpdate.js b/events/messageUpdate.js deleted file mode 100644 index 337bd25..0000000 --- a/events/messageUpdate.js +++ /dev/null @@ -1,37 +0,0 @@ -// Required components -const { event } = require('vylbot-core'); -const { MessageEmbed } = require('discord.js'); - -// Event variables -const embedColor = "0x3050ba"; -const logchannel = "message-logs"; - -// Event class -class messageupdate extends event { - constructor() { - // Set the event's run method - super("messageupdate"); - } - - // Run method - messageupdate(oldMessage, newMessage) { - // If the user is a bot or the content didn't change, return - if (newMessage.author.bot) return; - if (oldMessage.content == newMessage.content) return; - - // Create an embed with the message's information - const embed = new MessageEmbed() - .setTitle("Message Edited") - .setColor(embedColor) - .addField("User", `${newMessage.author} \`${newMessage.author.tag}\``) - .addField("Channel", newMessage.channel) - .addField("Before", `\`\`\`${oldMessage.content || "*none*"}\`\`\``) - .addField("After", `\`\`\`${newMessage.content || "*none*"}\`\`\``) - .setThumbnail(newMessage.author.displayAvatarURL({ type: 'png', dynamic: true })); - - // Send the embed into the log channel - newMessage.guild.channels.cache.find(channel => channel.name == logchannel).send(embed); - } -} - -module.exports = messageupdate; diff --git a/src/events/MessageEvents.ts b/src/events/MessageEvents.ts index 0d449ec..29dfe8d 100644 --- a/src/events/MessageEvents.ts +++ b/src/events/MessageEvents.ts @@ -9,6 +9,7 @@ export default class MessageEvents extends Event { public override messageDelete(message: Message) { if (!message.guild) return; + if (message.author.bot) return; const embed = new EventEmbed(message.guild, "Message Deleted"); embed.AddUser("User", message.author, true); From 2cc12d91be6f61ab80f4caf391ac7dcb4ffa0139 Mon Sep 17 00:00:00 2001 From: Vylpes <ethan@vylpes.com> Date: Fri, 24 Dec 2021 14:55:28 +0000 Subject: [PATCH 24/28] Feature/74 merge vylbot core (#80) * Merge VylBot-Core * Update commands to new system * Fix issue where events would not load --- .env.template | 2 - config.template.json | 61 - jest.config.js | 5 + package.json | 9 +- src/Register.ts | 37 + src/client/client.ts | 59 + src/client/events.ts | 76 + src/client/util.ts | 102 + src/commands/about.ts | 6 +- src/commands/ban.ts | 11 +- src/commands/clear.ts | 7 +- src/commands/eval.ts | 7 +- src/commands/help.ts | 7 +- src/commands/kick.ts | 9 +- src/commands/mute.ts | 9 +- src/commands/poll.ts | 7 +- src/commands/role.ts | 7 +- src/commands/rules.ts | 7 +- src/commands/unmute.ts | 9 +- src/commands/warn.ts | 9 +- src/contracts/IBaseResponse.ts | 4 + src/contracts/ICommandContext.ts | 7 + src/contracts/ICommandItem.ts | 6 + src/contracts/IEventItem.ts | 6 + src/events/MemberEvents.ts | 4 +- src/events/MemberEvents/GuildMemberUpdate.ts | 2 +- src/events/MessageEvents.ts | 4 +- src/helpers/{ => embeds}/ErrorEmbed.ts | 2 +- src/helpers/{ => embeds}/EventEmbed.ts | 2 - src/helpers/{ => embeds}/LogEmbed.ts | 4 +- src/helpers/{ => embeds}/PublicEmbed.ts | 2 +- src/type/command.ts | 15 + src/type/event.ts | 55 + src/vylbot.ts | 7 +- tests/__mocks/commands/noCategory.ts | 7 + tests/__mocks/commands/normal.ts | 8 + tests/__mocks/commands/roles.ts | 8 + tests/__mocks/events/normal.ts | 5 + tests/client/client.test.ts | 139 ++ tests/client/events.test.ts | 185 ++ tests/client/util.test.ts | 421 ++++ yarn.lock | 2176 +++++++++++++++++- 42 files changed, 3368 insertions(+), 147 deletions(-) delete mode 100644 config.template.json create mode 100644 jest.config.js create mode 100644 src/Register.ts create mode 100644 src/client/client.ts create mode 100644 src/client/events.ts create mode 100644 src/client/util.ts create mode 100644 src/contracts/IBaseResponse.ts create mode 100644 src/contracts/ICommandContext.ts create mode 100644 src/contracts/ICommandItem.ts create mode 100644 src/contracts/IEventItem.ts rename src/helpers/{ => embeds}/ErrorEmbed.ts (87%) rename src/helpers/{ => embeds}/EventEmbed.ts (93%) rename src/helpers/{ => embeds}/LogEmbed.ts (92%) rename src/helpers/{ => embeds}/PublicEmbed.ts (90%) create mode 100644 src/type/command.ts create mode 100644 src/type/event.ts create mode 100644 tests/__mocks/commands/noCategory.ts create mode 100644 tests/__mocks/commands/normal.ts create mode 100644 tests/__mocks/commands/roles.ts create mode 100644 tests/__mocks/events/normal.ts create mode 100644 tests/client/client.test.ts create mode 100644 tests/client/events.test.ts create mode 100644 tests/client/util.test.ts diff --git a/.env.template b/.env.template index 631f40c..22c5724 100644 --- a/.env.template +++ b/.env.template @@ -13,8 +13,6 @@ BOT_AUTHOR=Vylpes BOT_DATE=28 Nov 2021 BOT_OWNERID=147392775707426816 -CORE_VER=2.0.2 - FOLDERS_COMMANDS=src/commands FOLDERS_EVENTS=src/events diff --git a/config.template.json b/config.template.json deleted file mode 100644 index eb86869..0000000 --- a/config.template.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "token": "", - "prefix": "v!", - "commands": [ - "commands" - ], - "events": [ - "events" - ], - "about": { - "description": "Discord Bot for Vylpes' Den", - "version": "2.2", - "core-ver": "1.0.4", - "author": "Vylpes", - "date": "23/11/2021" - }, - "ban": { - "modrole": "Moderator", - "logchannel": "mod-logs" - }, - "clear": { - "modrole": "Moderator", - "logchannel": "mod-logs" - }, - "eval": { - "ownerid": "147392775707426816" - }, - "kick": { - "modrole": "Moderator", - "logchannel": "mod-logs" - }, - "mute": { - "modrole": "Moderator", - "logchannel": "mod-logs", - "muterole": "Muted" - }, - "partner": { - "adminrole": "Admin", - "partnersfile": "data/partner/partner.json" - }, - "rules": { - "adminrole": "Admin", - "rulesfile": "data/rules/rules.json" - }, - "unmute": { - "modrole": "Moderator", - "logchannel": "mod-logs", - "muterole": "Muted" - }, - "warn": { - "modrole": "Moderator", - "logchannel": "mod-logs" - }, - "role": { - "assignable": [ - "Notify", - "VotePings", - "ProjectUpdates" - ] - } -} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..5839600 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/package.json b/package.json index 6874877..746c568 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "typings": "./dist", "scripts": { "build": "tsc", - "start": "ts-node ./src/vylbot" + "start": "ts-node ./src/vylbot", + "test": "jest" }, "repository": { "type": "git", @@ -17,10 +18,14 @@ "bugs": "https://github.com/Vylpes/vylbot-app/issues", "homepage": "https://github.com/Vylpes/vylbot-app", "dependencies": { + "@types/jest": "^27.0.3", + "discord.js": "12.5.3", "dotenv": "^10.0.0", "emoji-regex": "^9.2.0", + "jest": "^27.4.5", + "jest-mock-extended": "^2.0.4", "random-bunny": "^2.0.0", - "vylbot-core": "^2.0.3" + "ts-jest": "^27.1.2" }, "devDependencies": { "@types/node": "^16.11.10", diff --git a/src/Register.ts b/src/Register.ts new file mode 100644 index 0000000..d8390bf --- /dev/null +++ b/src/Register.ts @@ -0,0 +1,37 @@ +import { CoreClient } from "./client/client"; +import About from "./commands/about"; +import Ban from "./commands/ban"; +import Clear from "./commands/clear"; +import Evaluate from "./commands/eval"; +import Help from "./commands/help"; +import Kick from "./commands/kick"; +import Mute from "./commands/mute"; +import Poll from "./commands/poll"; +import Role from "./commands/role"; +import Rules from "./commands/rules"; +import Unmute from "./commands/unmute"; +import Warn from "./commands/warn"; +import MemberEvents from "./events/MemberEvents"; +import MessageEvents from "./events/MessageEvents"; + +export default class Register { + public static RegisterCommands(client: CoreClient) { + client.RegisterCommand("about", new About()); + client.RegisterCommand("ban", new Ban()); + client.RegisterCommand("clear", new Clear()); + client.RegisterCommand("eval", new Evaluate()); + client.RegisterCommand("help", new Help()); + client.RegisterCommand("kick", new Kick()); + client.RegisterCommand("mute", new Mute()); + client.RegisterCommand("poll", new Poll()); + client.RegisterCommand("role", new Role()); + client.RegisterCommand("rules", new Rules()); + client.RegisterCommand("unmute", new Unmute()); + client.RegisterCommand("warn", new Warn()); + } + + public static RegisterEvents(client: CoreClient) { + client.RegisterEvent(new MemberEvents()); + client.RegisterEvent(new MessageEvents()); + } +} \ No newline at end of file diff --git a/src/client/client.ts b/src/client/client.ts new file mode 100644 index 0000000..f6c992b --- /dev/null +++ b/src/client/client.ts @@ -0,0 +1,59 @@ +import { Client } from "discord.js"; +import * as dotenv from "dotenv"; +import ICommandItem from "../contracts/ICommandItem"; +import IEventItem from "../contracts/IEventItem"; +import { Command } from "../type/command"; +import { Event } from "../type/event"; + +import { Events } from "./events"; +import { Util } from "./util"; + +export class CoreClient extends Client { + private _commandItems: ICommandItem[]; + private _eventItems: IEventItem[]; + + private _events: Events; + private _util: Util; + + constructor() { + super(); + dotenv.config(); + + this._commandItems = []; + this._eventItems = []; + + this._events = new Events(); + this._util = new Util(); + } + + public start() { + if (!process.env.BOT_TOKEN) throw "BOT_TOKEN is not defined in .env"; + if (!process.env.BOT_PREFIX) throw "BOT_PREFIX is not defined in .env"; + if (!process.env.FOLDERS_COMMANDS) throw "FOLDERS_COMMANDS is not defined in .env"; + if (!process.env.FOLDERS_EVENTS) throw "FOLDERS_EVENTS is not defined in .env"; + + super.on("message", (message) => this._events.onMessage(message, this._commandItems)); + super.on("ready", this._events.onReady); + + super.login(process.env.BOT_TOKEN); + + this._util.loadEvents(this, this._eventItems); + } + + public RegisterCommand(name: string, command: Command) { + const item: ICommandItem = { + Name: name, + Command: command, + }; + + this._commandItems.push(item); + } + + public RegisterEvent(event: Event) { + const item: IEventItem = { + Event: event, + }; + + this._eventItems.push(item); + } +} diff --git a/src/client/events.ts b/src/client/events.ts new file mode 100644 index 0000000..ca0d058 --- /dev/null +++ b/src/client/events.ts @@ -0,0 +1,76 @@ +import { Message } from "discord.js"; +import { IBaseResponse } from "../contracts/IBaseResponse"; +import ICommandItem from "../contracts/ICommandItem"; +import { Util } from "./util"; + +export interface IEventResponse extends IBaseResponse { + context?: { + prefix: string; + name: string; + args: string[]; + message: Message; + } +} + +export class Events { + private _util: Util; + + constructor() { + this._util = new Util(); + } + + // Emit when a message is sent + // Used to check for commands + public onMessage(message: Message, commands: ICommandItem[]): IEventResponse { + if (!message.guild) return { + valid: false, + message: "Message was not sent in a guild, ignoring.", + }; + + if (message.author.bot) return { + valid: false, + message: "Message was sent by a bot, ignoring.", + }; + + const prefix = process.env.BOT_PREFIX as string; + + if (message.content.substring(0, prefix.length).toLowerCase() == prefix.toLowerCase()) { + const args = message.content.substring(prefix.length).split(" "); + const name = args.shift(); + + if (!name) return { + valid: false, + message: "Command name was not found", + }; + + const res = this._util.loadCommand(name, args, message, commands); + + if (!res.valid) { + return { + valid: false, + message: res.message, + }; + } + + return { + valid: true, + context: { + prefix: prefix, + name: name, + args: args, + message: message, + }, + }; + } + + return { + valid: false, + message: "Message was not a command, ignoring.", + } + } + + // Emit when bot is logged in and ready to use + public onReady() { + console.log("Ready"); + } +} diff --git a/src/client/util.ts b/src/client/util.ts new file mode 100644 index 0000000..61ec0f9 --- /dev/null +++ b/src/client/util.ts @@ -0,0 +1,102 @@ +// Required Components +import { Client, Message } from "discord.js"; +import { readdirSync, existsSync } from "fs"; +import { IBaseResponse } from "../contracts/IBaseResponse"; +import { Command } from "../type/command"; +import { Event } from "../type/event"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandItem from "../contracts/ICommandItem"; +import IEventItem from "../contracts/IEventItem"; + +export interface IUtilResponse extends IBaseResponse { + context?: { + name: string; + args: string[]; + message: Message; + } +} + +// Util Class +export class Util { + public loadCommand(name: string, args: string[], message: Message, commands: ICommandItem[]): IUtilResponse { + if (!message.member) return { + valid: false, + message: "Member is not part of message", + }; + + const disabledCommands = process.env.COMMANDS_DISABLED?.split(','); + + if (disabledCommands?.find(x => x == name)) { + message.reply(process.env.COMMANDS_DISABLED_MESSAGE || "This command is disabled."); + + return { + valid: false, + message: "Command is disabled", + }; + } + + const folder = process.env.FOLDERS_COMMANDS; + + const item = commands.find(x => x.Name == name); + + if (!item) { + message.reply('Command not found'); + + return { + valid: false, + message: "Command not found" + }; + } + + const requiredRoles = item.Command._roles; + + for (const i in requiredRoles) { + if (!message.member.roles.cache.find(role => role.name == requiredRoles[i])) { + message.reply(`You require the \`${requiredRoles[i]}\` role to run this command`); + + return { + valid: false, + message: `You require the \`${requiredRoles[i]}\` role to run this command` + }; + } + } + + const context: ICommandContext = { + name: name, + args: args, + message: message + }; + + item.Command.execute(context); + + return { + valid: true, + context: context + } + } + + // Load the events + loadEvents(client: Client, events: IEventItem[]): IUtilResponse { + const folder = process.env.FOLDERS_EVENTS; + + events.forEach((e) => { + client.on('channelCreate', e.Event.channelCreate); + client.on('channelDelete', e.Event.channelDelete); + client.on('channelUpdate', e.Event.channelUpdate); + client.on('guildBanAdd', e.Event.guildBanAdd); + client.on('guildBanRemove', e.Event.guildBanRemove); + client.on('guildCreate', e.Event.guildCreate); + client.on('guildMemberAdd', e.Event.guildMemberAdd); + client.on('guildMemberRemove', e.Event.guildMemberRemove); + client.on('guildMemberUpdate', e.Event.guildMemberUpdate); + client.on('message', e.Event.message); + client.on('messageDelete', e.Event.messageDelete); + client.on('messageUpdate', e.Event.messageUpdate); + client.on('ready', e.Event.ready); + }); + + return { + valid: true + } + } +} diff --git a/src/commands/about.ts b/src/commands/about.ts index f30e490..7b75cf8 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -1,5 +1,6 @@ -import { Command, ICommandContext } from "vylbot-core"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class About extends Command { constructor() { @@ -10,7 +11,6 @@ export default class About extends Command { public override execute(context: ICommandContext) { const embed = new PublicEmbed(context, "About", "") .addField("Version", process.env.BOT_VER) - .addField("VylBot Core", process.env.CORE_VER) .addField("Author", process.env.BOT_AUTHOR) .addField("Date", process.env.BOT_DATE); diff --git a/src/commands/ban.ts b/src/commands/ban.ts index b0a1371..30a17fe 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -1,10 +1,11 @@ -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import ErrorMessages from "../constants/ErrorMessages"; -import LogEmbed from "../helpers/LogEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import LogEmbed from "../helpers/embeds/LogEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; +import { ICommandContext } from "../contracts/ICommandContext"; -export default class Bane extends Command { +export default class Ban extends Command { constructor() { super(); diff --git a/src/commands/clear.ts b/src/commands/clear.ts index 14015d4..cf940a7 100644 --- a/src/commands/clear.ts +++ b/src/commands/clear.ts @@ -1,7 +1,8 @@ -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import { TextChannel } from "discord.js"; -import PublicEmbed from "../helpers/PublicEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; +import { ICommandContext } from "../contracts/ICommandContext"; export default class Clear extends Command { constructor() { diff --git a/src/commands/eval.ts b/src/commands/eval.ts index 0c8154f..29e940c 100644 --- a/src/commands/eval.ts +++ b/src/commands/eval.ts @@ -1,6 +1,7 @@ -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class Evaluate extends Command { constructor() { diff --git a/src/commands/help.ts b/src/commands/help.ts index dc56258..6b0d148 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,8 +1,9 @@ import { existsSync, readdirSync } from "fs"; -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; import StringTools from "../helpers/StringTools"; +import { Command } from "../type/command"; interface ICommandData { Exists: boolean; diff --git a/src/commands/kick.ts b/src/commands/kick.ts index 1d9123f..fc3dd41 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -1,8 +1,9 @@ -import { Command, ICommandContext } from "vylbot-core"; import ErrorMessages from "../constants/ErrorMessages"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import LogEmbed from "../helpers/LogEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import LogEmbed from "../helpers/embeds/LogEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class Kick extends Command { constructor() { diff --git a/src/commands/mute.ts b/src/commands/mute.ts index 774e437..f18616f 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -1,8 +1,9 @@ -import { Command, ICommandContext } from "vylbot-core"; import ErrorMessages from "../constants/ErrorMessages"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import LogEmbed from "../helpers/LogEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import LogEmbed from "../helpers/embeds/LogEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class Mute extends Command { constructor() { diff --git a/src/commands/poll.ts b/src/commands/poll.ts index 3d53194..fb3ea55 100644 --- a/src/commands/poll.ts +++ b/src/commands/poll.ts @@ -1,6 +1,7 @@ -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class Poll extends Command { constructor() { diff --git a/src/commands/role.ts b/src/commands/role.ts index 53c15d2..a5992f7 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -1,7 +1,8 @@ -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Role as DiscordRole } from "discord.js"; +import { Command } from "../type/command"; +import { ICommandContext } from "../contracts/ICommandContext"; export default class Role extends Command { constructor() { diff --git a/src/commands/rules.ts b/src/commands/rules.ts index 9b9af9f..8578405 100644 --- a/src/commands/rules.ts +++ b/src/commands/rules.ts @@ -1,7 +1,8 @@ import { existsSync, readFileSync } from "fs"; -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; interface IRules { title?: string; diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts index 07fb03a..da870cd 100644 --- a/src/commands/unmute.ts +++ b/src/commands/unmute.ts @@ -1,8 +1,9 @@ -import { Command, ICommandContext } from "vylbot-core"; import ErrorMessages from "../constants/ErrorMessages"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import LogEmbed from "../helpers/LogEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import LogEmbed from "../helpers/embeds/LogEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class Unmute extends Command { constructor() { diff --git a/src/commands/warn.ts b/src/commands/warn.ts index fdd186f..542c12f 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -1,7 +1,8 @@ -import { Command, ICommandContext } from "vylbot-core"; -import ErrorEmbed from "../helpers/ErrorEmbed"; -import LogEmbed from "../helpers/LogEmbed"; -import PublicEmbed from "../helpers/PublicEmbed"; +import { ICommandContext } from "../contracts/ICommandContext"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; +import LogEmbed from "../helpers/embeds/LogEmbed"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; export default class Warn extends Command { constructor() { diff --git a/src/contracts/IBaseResponse.ts b/src/contracts/IBaseResponse.ts new file mode 100644 index 0000000..f709544 --- /dev/null +++ b/src/contracts/IBaseResponse.ts @@ -0,0 +1,4 @@ +export interface IBaseResponse { + valid: boolean; + message?: string; +} \ No newline at end of file diff --git a/src/contracts/ICommandContext.ts b/src/contracts/ICommandContext.ts new file mode 100644 index 0000000..78f0d17 --- /dev/null +++ b/src/contracts/ICommandContext.ts @@ -0,0 +1,7 @@ +import { Message } from "discord.js"; + +export interface ICommandContext { + name: string; + args: string[]; + message: Message; +} \ No newline at end of file diff --git a/src/contracts/ICommandItem.ts b/src/contracts/ICommandItem.ts new file mode 100644 index 0000000..c8246c9 --- /dev/null +++ b/src/contracts/ICommandItem.ts @@ -0,0 +1,6 @@ +import { Command } from "../type/command"; + +export default interface ICommandItem { + Name: string, + Command: Command, +} \ No newline at end of file diff --git a/src/contracts/IEventItem.ts b/src/contracts/IEventItem.ts new file mode 100644 index 0000000..51bb881 --- /dev/null +++ b/src/contracts/IEventItem.ts @@ -0,0 +1,6 @@ + +import { Event } from "../type/event"; + +export default interface IEventItem { + Event: Event, +} \ No newline at end of file diff --git a/src/events/MemberEvents.ts b/src/events/MemberEvents.ts index 696fa11..2aada07 100644 --- a/src/events/MemberEvents.ts +++ b/src/events/MemberEvents.ts @@ -1,6 +1,6 @@ -import { Event } from "vylbot-core"; +import { Event } from "../type/event"; import { GuildMember } from "discord.js"; -import EventEmbed from "../helpers/EventEmbed"; +import EventEmbed from "../helpers/embeds/EventEmbed"; import GuildMemberUpdate from "./MemberEvents/GuildMemberUpdate"; export default class MemberEvents extends Event { diff --git a/src/events/MemberEvents/GuildMemberUpdate.ts b/src/events/MemberEvents/GuildMemberUpdate.ts index ef2c530..2f15a16 100644 --- a/src/events/MemberEvents/GuildMemberUpdate.ts +++ b/src/events/MemberEvents/GuildMemberUpdate.ts @@ -1,5 +1,5 @@ import { GuildMember } from "discord.js"; -import EventEmbed from "../../helpers/EventEmbed"; +import EventEmbed from "../../helpers/embeds/EventEmbed"; export default class GuildMemberUpdate { private _oldMember: GuildMember; diff --git a/src/events/MessageEvents.ts b/src/events/MessageEvents.ts index 29dfe8d..0972a23 100644 --- a/src/events/MessageEvents.ts +++ b/src/events/MessageEvents.ts @@ -1,6 +1,6 @@ -import { Event } from "vylbot-core"; +import { Event } from "../type/event"; import { Message } from "discord.js"; -import EventEmbed from "../helpers/EventEmbed"; +import EventEmbed from "../helpers/embeds/EventEmbed"; export default class MessageEvents extends Event { constructor() { diff --git a/src/helpers/ErrorEmbed.ts b/src/helpers/embeds/ErrorEmbed.ts similarity index 87% rename from src/helpers/ErrorEmbed.ts rename to src/helpers/embeds/ErrorEmbed.ts index 9964369..ded3d2d 100644 --- a/src/helpers/ErrorEmbed.ts +++ b/src/helpers/embeds/ErrorEmbed.ts @@ -1,5 +1,5 @@ import { MessageEmbed } from "discord.js"; -import { ICommandContext } from "vylbot-core"; +import { ICommandContext } from "../../contracts/ICommandContext"; export default class ErrorEmbed extends MessageEmbed { private _context: ICommandContext; diff --git a/src/helpers/EventEmbed.ts b/src/helpers/embeds/EventEmbed.ts similarity index 93% rename from src/helpers/EventEmbed.ts rename to src/helpers/embeds/EventEmbed.ts index 25141d8..8b4d364 100644 --- a/src/helpers/EventEmbed.ts +++ b/src/helpers/embeds/EventEmbed.ts @@ -1,6 +1,4 @@ import { MessageEmbed, TextChannel, User, Guild } from "discord.js"; -import ErrorMessages from "../constants/ErrorMessages"; -import ErrorEmbed from "./ErrorEmbed"; export default class EventEmbed extends MessageEmbed { private _guild: Guild; diff --git a/src/helpers/LogEmbed.ts b/src/helpers/embeds/LogEmbed.ts similarity index 92% rename from src/helpers/LogEmbed.ts rename to src/helpers/embeds/LogEmbed.ts index c96acdc..00eb17a 100644 --- a/src/helpers/LogEmbed.ts +++ b/src/helpers/embeds/LogEmbed.ts @@ -1,6 +1,6 @@ import { MessageEmbed, TextChannel, User } from "discord.js"; -import { ICommandContext } from "vylbot-core"; -import ErrorMessages from "../constants/ErrorMessages"; +import ErrorMessages from "../../constants/ErrorMessages"; +import { ICommandContext } from "../../contracts/ICommandContext"; import ErrorEmbed from "./ErrorEmbed"; export default class LogEmbed extends MessageEmbed { diff --git a/src/helpers/PublicEmbed.ts b/src/helpers/embeds/PublicEmbed.ts similarity index 90% rename from src/helpers/PublicEmbed.ts rename to src/helpers/embeds/PublicEmbed.ts index da278e5..4a02f17 100644 --- a/src/helpers/PublicEmbed.ts +++ b/src/helpers/embeds/PublicEmbed.ts @@ -1,5 +1,5 @@ import { MessageEmbed } from "discord.js"; -import { ICommandContext } from "vylbot-core"; +import { ICommandContext } from "../../contracts/ICommandContext"; export default class PublicEmbed extends MessageEmbed { private _context: ICommandContext; diff --git a/src/type/command.ts b/src/type/command.ts new file mode 100644 index 0000000..5a3ae45 --- /dev/null +++ b/src/type/command.ts @@ -0,0 +1,15 @@ +import { ICommandContext } from "../contracts/ICommandContext"; + +export class Command { + public _roles: string[]; + + public _category?: string; + + constructor() { + this._roles = []; + } + + public execute(context: ICommandContext) { + + } +} diff --git a/src/type/event.ts b/src/type/event.ts new file mode 100644 index 0000000..cba08c2 --- /dev/null +++ b/src/type/event.ts @@ -0,0 +1,55 @@ +import { Channel, Guild, User, GuildMember, Message, PartialDMChannel, PartialGuildMember, PartialMessage } from "discord.js"; + +export class Event { + public channelCreate(channel: Channel) { + + } + + public channelDelete(channel: Channel | PartialDMChannel) { + + } + + public channelUpdate(oldChannel: Channel, newChannel: Channel) { + + } + + public guildBanAdd(guild: Guild, user: User) { + + } + + public guildBanRemove(guild: Guild, user: User) { + + } + + public guildCreate(guild: Guild) { + + } + + public guildMemberAdd(member: GuildMember) { + + } + + public guildMemberRemove(member: GuildMember | PartialGuildMember) { + + } + + public guildMemberUpdate(oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) { + + } + + public message(message: Message) { + + } + + public messageDelete(message: Message | PartialMessage) { + + } + + public messageUpdate(oldMessage: Message | PartialMessage, newMessage: Message | PartialMessage) { + + } + + public ready() { + + } +} diff --git a/src/vylbot.ts b/src/vylbot.ts index 80f7a25..56cbdc4 100644 --- a/src/vylbot.ts +++ b/src/vylbot.ts @@ -1,5 +1,6 @@ -import { CoreClient } from "vylbot-core"; +import { CoreClient } from "./client/client"; import * as dotenv from "dotenv"; +import Register from "./Register"; dotenv.config(); @@ -22,4 +23,8 @@ requiredConfigs.forEach(config => { }); const client = new CoreClient(); + +Register.RegisterCommands(client); +Register.RegisterEvents(client); + client.start(); \ No newline at end of file diff --git a/tests/__mocks/commands/noCategory.ts b/tests/__mocks/commands/noCategory.ts new file mode 100644 index 0000000..e71cd53 --- /dev/null +++ b/tests/__mocks/commands/noCategory.ts @@ -0,0 +1,7 @@ +import { Command } from "../../../src/type/command"; + +export default class noCategory extends Command { + constructor() { + super(); + } +} diff --git a/tests/__mocks/commands/normal.ts b/tests/__mocks/commands/normal.ts new file mode 100644 index 0000000..3e3a74f --- /dev/null +++ b/tests/__mocks/commands/normal.ts @@ -0,0 +1,8 @@ +import { Command } from "../../../src/type/command"; + +export default class normal extends Command { + constructor() { + super(); + this._category = "General"; + } +} diff --git a/tests/__mocks/commands/roles.ts b/tests/__mocks/commands/roles.ts new file mode 100644 index 0000000..1e0321e --- /dev/null +++ b/tests/__mocks/commands/roles.ts @@ -0,0 +1,8 @@ +import { Command } from "../../../src/type/command"; + +export default class roles extends Command { + constructor() { + super(); + this._roles = [ "Moderator" ]; + } +} diff --git a/tests/__mocks/events/normal.ts b/tests/__mocks/events/normal.ts new file mode 100644 index 0000000..8a85eaa --- /dev/null +++ b/tests/__mocks/events/normal.ts @@ -0,0 +1,5 @@ +import { Event } from "../../../src/type/event"; + +export class normal extends Event { + public override channelCreate() {} +} \ No newline at end of file diff --git a/tests/client/client.test.ts b/tests/client/client.test.ts new file mode 100644 index 0000000..712cb41 --- /dev/null +++ b/tests/client/client.test.ts @@ -0,0 +1,139 @@ +import { CoreClient } from "../../src/client/client"; + +import { Client } from "discord.js"; +import * as dotenv from "dotenv"; +import { Events } from "../../src/client/events"; +import { Util } from "../../src/client/util"; + +jest.mock("discord.js"); +jest.mock("dotenv"); +jest.mock("../../src/client/events"); +jest.mock("../../src/client/util"); + +describe('Constructor', () => { + test('Constructor_ExpectSuccessfulInitialisation', () => { + const coreClient = new CoreClient(); + + expect(coreClient).toBeInstanceOf(Client); + expect(dotenv.config).toBeCalledTimes(1); + expect(Events).toBeCalledTimes(1); + expect(Util).toBeCalledTimes(1); + }); +}); + +describe('Start', () => { + test('Given Env Is Valid, Expect Successful Start', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).not.toThrow(); + expect(coreClient.on).toBeCalledWith("message", expect.any(Function)); + expect(coreClient.on).toBeCalledWith("ready", expect.any(Function)); + }); + + test('Given BOT_TOKEN Is Null, Expect Failure', () => { + process.env = { + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("BOT_TOKEN is not defined in .env"); + }); + + test('Given BOT_TOKEN Is Empty, Expect Failure', () => { + process.env = { + BOT_TOKEN: '', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("BOT_TOKEN is not defined in .env"); + }); + + test('Given BOT_PREFIX Is Null, Expect Failure', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("BOT_PREFIX is not defined in .env"); + }); + + test('Given BOT_PREFIX Is Empty, Expect Failure', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("BOT_PREFIX is not defined in .env"); + }); + + test('Given FOLDERS_COMMANDS Is Null, Expect Failure', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("FOLDERS_COMMANDS is not defined in .env"); + }); + + test('Given FOLDERS_COMMANDS Is Empty, Expect Failure', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: '', + FOLDERS_EVENTS: 'events', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("FOLDERS_COMMANDS is not defined in .env"); + }); + + test('Given FOLDERS_EVENTS Is Null, Expect Failure', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("FOLDERS_EVENTS is not defined in .env"); + }); + + test('Given FOLDERS_EVENTS Is Empty, Expect Failure', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: '', + } + + const coreClient = new CoreClient(); + + expect(() => coreClient.start()).toThrow("FOLDERS_EVENTS is not defined in .env"); + }); +}); \ No newline at end of file diff --git a/tests/client/events.test.ts b/tests/client/events.test.ts new file mode 100644 index 0000000..69baac9 --- /dev/null +++ b/tests/client/events.test.ts @@ -0,0 +1,185 @@ +import { Events } from "../../src/client/events"; + +import { Message, Client, TextChannel, Guild, SnowflakeUtil, DMChannel } from "discord.js"; +import { Util } from "../../src/client/util"; + +jest.mock("../../src/client/util"); + +beforeEach(() => { + Util.prototype.loadCommand = jest.fn(); +}); + +describe('OnMessage', () => { + test('Given Message Is Valid Expect Message Sent', async () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + }; + + Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true }); + + const message = { + guild: {}, + author: { + bot: false, + }, + content: "!test first", + } as unknown as Message; + + const events = new Events(); + + const result = await events.onMessage(message); + + expect(result.valid).toBeTruthy(); + + expect(result.context?.prefix).toBe('!'); + expect(result.context?.name).toBe('test'); + expect(result.context?.args.length).toBe(1); + expect(result.context?.args[0]).toBe('first'); + expect(result.context?.message).toBe(message); + }); + + test('Given Guild Is Null, Expect Failed Result', async () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true }); + + const message = { + guild: null, + author: { + bot: false, + }, + content: "!test first", + } as unknown as Message; + + const events = new Events(); + + const result = await events.onMessage(message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Message was not sent in a guild, ignoring."); + }); + + test('Given Author Is A Bot, Expect Failed Result', async () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true }); + + const message = { + guild: {}, + author: { + bot: true, + }, + content: "!test first", + } as unknown as Message; + + const events = new Events(); + + const result = await events.onMessage(message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Message was sent by a bot, ignoring."); + }); + + test('Given Message Content Was Not A Command, Expect Failed Result', async () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true }); + + const message = { + guild: {}, + author: { + bot: false, + }, + content: "This is a standard message", + } as unknown as Message; + + const events = new Events(); + + const result = await events.onMessage(message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Message was not a command, ignoring."); + }); + + test('Given Message Had No Command Name, Expect Failed Result', async () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true }); + + const message = { + guild: {}, + author: { + bot: false, + }, + content: "!", + } as unknown as Message; + + const events = new Events(); + + const result = await events.onMessage(message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Command name was not found"); + }); + + test('Given Command Failed To Execute, Expect Failed Result', async () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: false, message: "Command failed" }); + + const message = { + guild: {}, + author: { + bot: false, + }, + content: "!test first", + } as unknown as Message; + + const events = new Events(); + + const result = await events.onMessage(message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Command failed"); + }); +}); + +describe('OnReady', () => { + test('Expect Console Log', () => { + console.log = jest.fn(); + + const events = new Events(); + + events.onReady(); + + expect(console.log).toBeCalledWith("Ready"); + }); +}); diff --git a/tests/client/util.test.ts b/tests/client/util.test.ts new file mode 100644 index 0000000..fe046e2 --- /dev/null +++ b/tests/client/util.test.ts @@ -0,0 +1,421 @@ +import { Util } from "../../src/client/util"; + +import { Client, Guild, Message, Role, SnowflakeUtil, TextChannel, User } from "discord.js"; +import fs from "fs"; + +jest.mock("fs"); + +beforeEach(() => { + fs.existsSync = jest.fn(); +}); + +describe('LoadCommand', () => { + test('Given Successful Exection, Expect Successful Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeTruthy(); + }); + + test('Given Member Is Null, Expect Failed Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: null + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Member is not part of message"); + }); + + test('Given Folder Does Not Exist, Expect Failed Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(false); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Command folder does not exist"); + }); + + test('Given File Does Not Exist, Expect Failed Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValueOnce(true) + .mockReturnValue(false); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("File does not exist"); + }); + + test('Given User Does Have Role, Expect Successful Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("roles", [ "first" ], message); + + expect(result.valid).toBeTruthy(); + }); + + test('Given User Does Not Have Role, Expect Failed Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(false), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("roles", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("You require the `Moderator` role to run this command"); + }); + + test('Given Command Category Is Null, Expect Successful Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("noCategory", [ "first" ], message); + + expect(result.valid).toBeTruthy(); + }); + + test('Given command is set to disabled, Expect command to not fire', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + COMMANDS_DISABLED: 'normal', + COMMANDS_DISABLED_MESSAGE: 'disabled', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const messageReply = jest.spyOn(message, 'reply'); + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Command is disabled"); + expect(messageReply).toBeCalledWith("disabled"); + }); + + test('Given command COMMANDS_DISABLED_MESSAGE is empty, Expect default message sent', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + COMMANDS_DISABLED: 'normal', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const messageReply = jest.spyOn(message, 'reply'); + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Command is disabled"); + expect(messageReply).toBeCalledWith("This command is disabled."); + }); + + test('Given a different command is disabled, Expect command to still fire', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + COMMANDS_DISABLED: 'anything', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeTruthy(); + }); + + test('Given a different command is disabled with this one, Expect command to not fire', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + COMMANDS_DISABLED: 'normal,anything,', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + + const message = { + member: { + roles: { + cache: { + find: jest.fn().mockReturnValue(true), + } + }, + }, + reply: jest.fn(), + } as unknown as Message; + + const util = new Util(); + + const result = util.loadCommand("normal", [ "first" ], message); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Command is disabled"); + }); +}); + +describe('LoadEvents', () => { + test('Given Events Are Loaded, Expect Successful Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + fs.readdirSync = jest.fn().mockReturnValue(["normal.ts"]); + + const client = { + on: jest.fn(), + } as unknown as Client; + + const util = new Util(); + + const result = util.loadEvents(client); + + const clientOn = jest.spyOn(client, 'on'); + + expect(result.valid).toBeTruthy(); + expect(clientOn).toBeCalledTimes(13); + }); + + test('Given No Events Found, Expect Successful Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(true); + fs.readdirSync = jest.fn().mockReturnValue(["normal"]); + + const client = { + on: jest.fn(), + } as unknown as Client; + + const util = new Util(); + + const result = util.loadEvents(client); + + const clientOn = jest.spyOn(client, 'on'); + + expect(result.valid).toBeTruthy(); + expect(clientOn).toBeCalledTimes(0); + }); + + test('Given Event Folder Does Not Exist, Expect Failed Result', () => { + process.env = { + BOT_TOKEN: 'TOKEN', + BOT_PREFIX: '!', + FOLDERS_COMMANDS: 'commands', + FOLDERS_EVENTS: 'events', + } + + process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); + fs.existsSync = jest.fn().mockReturnValue(false); + fs.readdirSync = jest.fn().mockReturnValue(["normal.ts"]); + + const client = { + on: jest.fn(), + } as unknown as Client; + + const util = new Util(); + + const result = util.loadEvents(client); + + expect(result.valid).toBeFalsy(); + expect(result.message).toBe("Event folder does not exist"); + }); +}); diff --git a/yarn.lock b/yarn.lock index 1b8040a..e95e211 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,12 +9,148 @@ dependencies: "@babel/highlight" "^7.10.4" +"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== + dependencies: + "@babel/highlight" "^7.16.0" + +"@babel/compat-data@^7.16.0": + version "7.16.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.7.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c" + integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.5" + "@babel/helper-compilation-targets" "^7.16.3" + "@babel/helper-module-transforms" "^7.16.5" + "@babel/helpers" "^7.16.5" + "@babel/parser" "^7.16.5" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.5" + "@babel/types" "^7.16.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.16.5", "@babel/generator@^7.7.2": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf" + integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA== + dependencies: + "@babel/types" "^7.16.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-compilation-targets@^7.16.3": + version "7.16.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" + integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== + dependencies: + "@babel/compat-data" "^7.16.0" + "@babel/helper-validator-option" "^7.14.5" + browserslist "^4.17.5" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8" + integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-function-name@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== + dependencies: + "@babel/helper-get-function-arity" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-get-function-arity@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-hoist-variables@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-imports@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-transforms@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29" + integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ== + dependencies: + "@babel/helper-environment-visitor" "^7.16.5" + "@babel/helper-module-imports" "^7.16.0" + "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-validator-identifier" "^7.15.7" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.5" + "@babel/types" "^7.16.0" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074" + integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ== + +"@babel/helper-simple-access@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" + integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-split-export-declaration@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== + dependencies: + "@babel/types" "^7.16.0" + "@babel/helper-validator-identifier@^7.15.7": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== -"@babel/highlight@^7.10.4": +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helpers@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd" + integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw== + dependencies: + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.5" + "@babel/types" "^7.16.0" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== @@ -23,6 +159,140 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.5", "@babel/parser@^7.7.2": + version "7.16.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" + integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.5.tgz#f47a33e4eee38554f00fb6b2f894fa1f5649b0b3" + integrity sha512-/d4//lZ1Vqb4mZ5xTep3dDK888j7BGM/iKqBmndBaoYAFPlPKrGU608VVBz5JeyAb6YQDjRu1UKqj86UhwWVgw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.5" + +"@babel/template@^7.16.0", "@babel/template@^7.3.3": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.16.5", "@babel/traverse@^7.7.2": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3" + integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.5" + "@babel/helper-environment-visitor" "^7.16.5" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.5" + "@babel/types" "^7.16.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -78,11 +348,210 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + 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" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.2.tgz#7a95612d38c007ddb528ee446fe5e5e785e685ce" + integrity sha512-xknHThRsPB/To1FUbi6pCe43y58qFC03zfb6R7fDb/FfC7k2R3i1l+izRBJf8DI46KhYGRaF14Eo9A3qbBoixg== + dependencies: + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.4.2" + jest-util "^27.4.2" + slash "^3.0.0" + +"@jest/core@^27.4.5": + version "27.4.5" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.4.5.tgz#cae2dc34259782f4866c6606c3b480cce920ed4c" + integrity sha512-3tm/Pevmi8bDsgvo73nX8p/WPng6KWlCyScW10FPEoN1HU4pwI83tJ3TsFvi1FfzsjwUlMNEPowgb/rPau/LTQ== + dependencies: + "@jest/console" "^27.4.2" + "@jest/reporters" "^27.4.5" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.5" + "@jest/types" "^27.4.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^27.4.2" + jest-config "^27.4.5" + jest-haste-map "^27.4.5" + jest-message-util "^27.4.2" + jest-regex-util "^27.4.0" + jest-resolve "^27.4.5" + jest-resolve-dependencies "^27.4.5" + jest-runner "^27.4.5" + jest-runtime "^27.4.5" + jest-snapshot "^27.4.5" + jest-util "^27.4.2" + jest-validate "^27.4.2" + jest-watcher "^27.4.2" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.4.4": + version "27.4.4" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.4.4.tgz#66ebebc79673d84aad29d2bb70a8c51e6c29bb4d" + integrity sha512-q+niMx7cJgt/t/b6dzLOh4W8Ef/8VyKG7hxASK39jakijJzbFBGpptx3RXz13FFV7OishQ9lTbv+dQ5K3EhfDQ== + dependencies: + "@jest/fake-timers" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/node" "*" + jest-mock "^27.4.2" + +"@jest/fake-timers@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.4.2.tgz#d217f86c3ba2027bf29e0b731fd0cb761a72d093" + integrity sha512-f/Xpzn5YQk5adtqBgvw1V6bF8Nx3hY0OIRRpCvWcfPl0EAjdqWPdhH3t/3XpiWZqtjIEHDyMKP9ajpva1l4Zmg== + dependencies: + "@jest/types" "^27.4.2" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.4.2" + jest-mock "^27.4.2" + jest-util "^27.4.2" + +"@jest/globals@^27.4.4": + version "27.4.4" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.4.4.tgz#fe501a80c23ea2dab585c42be2a519bb5e38530d" + integrity sha512-bqpqQhW30BOreXM8bA8t8JbOQzsq/WnPTnBl+It3UxAD9J8yxEAaBEylHx1dtBapAr/UBk8GidXbzmqnee8tYQ== + dependencies: + "@jest/environment" "^27.4.4" + "@jest/types" "^27.4.2" + expect "^27.4.2" + +"@jest/reporters@^27.4.5": + version "27.4.5" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.4.5.tgz#e229acca48d18ea39e805540c1c322b075ae63ad" + integrity sha512-3orsG4vi8zXuBqEoy2LbnC1kuvkg1KQUgqNxmxpQgIOQEPeV0onvZu+qDQnEoX8qTQErtqn/xzcnbpeTuOLSiA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.4.2" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.5" + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^27.4.5" + jest-resolve "^27.4.5" + jest-util "^27.4.2" + jest-worker "^27.4.5" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + +"@jest/source-map@^27.4.0": + version "27.4.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.4.0.tgz#2f0385d0d884fb3e2554e8f71f8fa957af9a74b6" + integrity sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + +"@jest/test-result@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.4.2.tgz#05fd4a5466ec502f3eae0b39dff2b93ea4d5d9ec" + integrity sha512-kr+bCrra9jfTgxHXHa2UwoQjxvQk3Am6QbpAiJ5x/50LW8llOYrxILkqY0lZRW/hu8FXesnudbql263+EW9iNA== + dependencies: + "@jest/console" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.4.5": + version "27.4.5" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.4.5.tgz#1d7e026844d343b60d2ca7fd82c579a17b445d7d" + integrity sha512-n5woIn/1v+FT+9hniymHPARA9upYUmfi5Pw9ewVwXCDlK4F5/Gkees9v8vdjGdAIJ2MPHLHodiajLpZZanWzEQ== + dependencies: + "@jest/test-result" "^27.4.2" + graceful-fs "^4.2.4" + jest-haste-map "^27.4.5" + jest-runtime "^27.4.5" + +"@jest/transform@^27.4.5": + version "27.4.5" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.4.5.tgz#3dfe2e3680cd4aa27356172bf25617ab5b94f195" + integrity sha512-PuMet2UlZtlGzwc6L+aZmR3I7CEBpqadO03pU40l2RNY2fFJ191b9/ITB44LNOhVtsyykx0OZvj0PCyuLm7Eew== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.4.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^27.4.5" + jest-regex-util "^27.4.0" + jest-util "^27.4.2" + micromatch "^4.0.4" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^27.4.2": + version "27.4.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.4.2.tgz#96536ebd34da6392c2b7c7737d693885b5dd44a5" + integrity sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@sindresorhus/is@^4.0.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.0.tgz#667bfc6186ae7c9e0b45a08960c551437176e1ca" integrity sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw== +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -90,6 +559,11 @@ dependencies: defer-to-connect "^2.0.0" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -110,6 +584,39 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.17" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64" + integrity sha512-6zzkezS9QEIL8yCBvXWxPTJPNuMeECJVxSOhxNY/jfq9LxOTHivaYTqr37n9LknWWRTIkzqH2UilS5QFvfa90A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" + integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" @@ -120,11 +627,45 @@ "@types/node" "*" "@types/responselike" "*" +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^27.0.3": + version "27.0.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.3.tgz#0cf9dfe9009e467f70a342f0f94ead19842a783a" + integrity sha512-cmmwv9t7gBYt7hNKH5Spu7Kuu/DotGa+Ff+JGRKZ4db5eh8PnKS4LuebJ3YLUoyOyIHraTGyULn23YtEAm0VSg== + dependencies: + jest-diff "^27.0.0" + pretty-format "^27.0.0" + "@types/keyv@*": version "3.1.3" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41" @@ -142,6 +683,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.10.tgz#2e3ad0a680d96367103d3e670d41c2fed3da61ae" integrity sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA== +"@types/prettier@^2.1.5": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281" + integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA== + "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -149,6 +695,28 @@ dependencies: "@types/node" "*" +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +abab@^2.0.3, abab@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -156,26 +724,46 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^7.4.0: +acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.4.1: +acorn@^8.2.4, acorn@^8.4.1: version "8.6.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -201,6 +789,13 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -220,6 +815,19 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -242,6 +850,67 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +babel-jest@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.4.5.tgz#d38bd0be8ea71d8b97853a5fc9f76deeb095c709" + integrity sha512-3uuUTjXbgtODmSv/DXO9nZfD52IyC2OYTFaXGRzL0kpykzroaquCrD5+lZNafTvZlnNqZHt5pb0M08qVBZnsnA== + dependencies: + "@jest/transform" "^27.4.5" + "@jest/types" "^27.4.2" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^27.4.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + +babel-plugin-istanbul@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + 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" + +babel-plugin-jest-hoist@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz#d7831fc0f93573788d80dee7e682482da4c730d6" + integrity sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + 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" + +babel-preset-jest@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz#70d0e676a282ccb200fbabd7f415db5fdf393bca" + integrity sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg== + dependencies: + babel-plugin-jest-hoist "^27.4.0" + babel-preset-current-node-syntax "^1.0.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -255,6 +924,48 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserslist@^4.17.5: + version "4.19.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -278,6 +989,21 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" + integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== + +caniuse-lite@^1.0.30001286: + version "1.0.30001291" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz#08a8d2cfea0b2cf2e1d94dd795942d0daef6108c" + integrity sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA== + chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -295,6 +1021,30 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" @@ -302,6 +1052,16 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -338,12 +1098,19 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -352,6 +1119,39 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@4, debug@^4.1.0: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + debug@^4.0.1, debug@^4.1.1: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -359,6 +1159,11 @@ debug@^4.0.1, debug@^4.1.1: dependencies: ms "2.1.2" +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -366,11 +1171,21 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -deep-is@^0.1.3: +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + defer-to-connect@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" @@ -381,12 +1196,22 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" + integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -discord.js@^12.3.1: +discord.js@12.5.3: version "12.5.3" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.5.3.tgz#56820d473c24320871df9ea0bbc6b462f21cf85c" integrity sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw== @@ -407,11 +1232,28 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +electron-to-chromium@^1.4.17: + version "1.4.25" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.25.tgz#ce95e6678f8c6893ae892c7e95a5000e83f1957f" + integrity sha512-bTwub9Y/76EiNmfaiJih+hAy6xn7Ns95S4KvI2NuKNOz8TEEKKQUu44xuy0PYMudjM9zdjKRS1bitsUvHTfuUg== + +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -436,16 +1278,38 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -526,7 +1390,7 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -565,21 +1429,60 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + 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" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expect@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.4.2.tgz#4429b0f7e307771d176de9bdf23229b101db6ef6" + integrity sha512-BjAXIDC6ZOW+WBFNg96J22D27Nq5ohn+oGcuP2rtOtcjuxNoV9McpQ60PcQWhdFOSBIQdR72e+4HdnbZTFSTyg== + dependencies: + "@jest/types" "^27.4.2" + ansi-styles "^5.0.0" + jest-get-type "^27.4.0" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-regex-util "^27.4.0" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -587,6 +1490,21 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -600,16 +1518,50 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +fsevents@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -617,6 +1569,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -631,7 +1588,7 @@ glob-parent@^6.0.0: dependencies: is-glob "^4.0.3" -glob@^7.1.3: +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -643,6 +1600,11 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + globals@^13.6.0, globals@^13.9.0: version "13.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" @@ -667,6 +1629,11 @@ got@^11.8.3: p-cancelable "^2.0.0" responselike "^2.0.0" +graceful-fs@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -677,11 +1644,39 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" @@ -690,6 +1685,26 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -703,6 +1718,14 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" +import-local@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" + integrity sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -721,6 +1744,13 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-core-module@^2.2.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== + dependencies: + has "^1.0.3" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -731,6 +1761,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -738,11 +1773,501 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-instrument@^5.0.4: + version "5.1.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + 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" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.1.tgz#7085857f17d2441053c6ce5c3b8fdf6882289397" + integrity sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.4.2.tgz#da2547ea47c6e6a5f6ed336151bd2075736eb4a5" + integrity sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A== + dependencies: + "@jest/types" "^27.4.2" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.4.5.tgz#70bfb78e0200cab9b84747bf274debacaa538467" + integrity sha512-eTNWa9wsvBwPykhMMShheafbwyakcdHZaEYh5iRrQ0PFJxkDP/e3U/FvzGuKWu2WpwUA3C3hPlfpuzvOdTVqnw== + dependencies: + "@jest/environment" "^27.4.4" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.4.2" + is-generator-fn "^2.0.0" + jest-each "^27.4.2" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-runtime "^27.4.5" + jest-snapshot "^27.4.5" + jest-util "^27.4.2" + pretty-format "^27.4.2" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.4.5.tgz#8708f54c28d13681f3255ec9026a2b15b03d41e8" + integrity sha512-hrky3DSgE0u7sQxaCL7bdebEPHx5QzYmrGuUjaPLmPE8jx5adtvGuOlRspvMoVLTTDOHRnZDoRLYJuA+VCI7Hg== + dependencies: + "@jest/core" "^27.4.5" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + jest-config "^27.4.5" + jest-util "^27.4.2" + jest-validate "^27.4.2" + prompts "^2.0.1" + yargs "^16.2.0" + +jest-config@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.4.5.tgz#77ed7f2ba7bcfd7d740ade711d0d13512e08a59e" + integrity sha512-t+STVJtPt+fpqQ8GBw850NtSQbnDOw/UzdPfzDaHQ48/AylQlW7LHj3dH+ndxhC1UxJ0Q3qkq7IH+nM1skwTwA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^27.4.5" + "@jest/types" "^27.4.2" + babel-jest "^27.4.5" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-circus "^27.4.5" + jest-environment-jsdom "^27.4.4" + jest-environment-node "^27.4.4" + jest-get-type "^27.4.0" + jest-jasmine2 "^27.4.5" + jest-regex-util "^27.4.0" + jest-resolve "^27.4.5" + jest-runner "^27.4.5" + jest-util "^27.4.2" + jest-validate "^27.4.2" + micromatch "^4.0.4" + pretty-format "^27.4.2" + slash "^3.0.0" + +jest-diff@^27.0.0, jest-diff@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.2.tgz#786b2a5211d854f848e2dcc1e324448e9481f36f" + integrity sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.4.0" + jest-get-type "^27.4.0" + pretty-format "^27.4.2" + +jest-docblock@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.4.0.tgz#06c78035ca93cbbb84faf8fce64deae79a59f69f" + integrity sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.4.2.tgz#19364c82a692d0d26557642098d1f4619c9ee7d3" + integrity sha512-53V2MNyW28CTruB3lXaHNk6PkiIFuzdOC9gR3C6j8YE/ACfrPnz+slB0s17AgU1TtxNzLuHyvNlLJ+8QYw9nBg== + dependencies: + "@jest/types" "^27.4.2" + chalk "^4.0.0" + jest-get-type "^27.4.0" + jest-util "^27.4.2" + pretty-format "^27.4.2" + +jest-environment-jsdom@^27.4.4: + version "27.4.4" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.4.4.tgz#94f738e99514d7a880e8ed8e03e3a321d43b49db" + integrity sha512-cYR3ndNfHBqQgFvS1RL7dNqSvD//K56j/q1s2ygNHcfTCAp12zfIromO1w3COmXrxS8hWAh7+CmZmGCIoqGcGA== + dependencies: + "@jest/environment" "^27.4.4" + "@jest/fake-timers" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/node" "*" + jest-mock "^27.4.2" + jest-util "^27.4.2" + jsdom "^16.6.0" + +jest-environment-node@^27.4.4: + version "27.4.4" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.4.4.tgz#42fe5e3b224cb69b99811ebf6f5eaa5a59618514" + integrity sha512-D+v3lbJ2GjQTQR23TK0kY3vFVmSeea05giInI41HHOaJnAwOnmUHTZgUaZL+VxUB43pIzoa7PMwWtCVlIUoVoA== + dependencies: + "@jest/environment" "^27.4.4" + "@jest/fake-timers" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/node" "*" + jest-mock "^27.4.2" + jest-util "^27.4.2" + +jest-get-type@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.4.0.tgz#7503d2663fffa431638337b3998d39c5e928e9b5" + integrity sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ== + +jest-haste-map@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.5.tgz#c2921224a59223f91e03ec15703905978ef0cc1a" + integrity sha512-oJm1b5qhhPs78K24EDGifWS0dELYxnoBiDhatT/FThgB9yxqUm5F6li3Pv+Q+apMBmmPNzOBnZ7ZxWMB1Leq1Q== + dependencies: + "@jest/types" "^27.4.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^27.4.0" + jest-serializer "^27.4.0" + jest-util "^27.4.2" + jest-worker "^27.4.5" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.4.5.tgz#ff79d11561679ff6c89715b0cd6b1e8c0dfbc6dc" + integrity sha512-oUnvwhJDj2LhOiUB1kdnJjkx8C5PwgUZQb9urF77mELH9DGR4e2GqpWQKBOYXWs5+uTN9BGDqRz3Aeg5Wts7aw== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^27.4.4" + "@jest/source-map" "^27.4.0" + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.4.2" + is-generator-fn "^2.0.0" + jest-each "^27.4.2" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-runtime "^27.4.5" + jest-snapshot "^27.4.5" + jest-util "^27.4.2" + pretty-format "^27.4.2" + throat "^6.0.1" + +jest-leak-detector@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.4.2.tgz#7fc3120893a7a911c553f3f2bdff9faa4454abbb" + integrity sha512-ml0KvFYZllzPBJWDei3mDzUhyp/M4ubKebX++fPaudpe8OsxUE+m+P6ciVLboQsrzOCWDjE20/eXew9QMx/VGw== + dependencies: + jest-get-type "^27.4.0" + pretty-format "^27.4.2" + +jest-matcher-utils@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.2.tgz#d17c5038607978a255e0a9a5c32c24e984b6c60b" + integrity sha512-jyP28er3RRtMv+fmYC/PKG8wvAmfGcSNproVTW2Y0P/OY7/hWUOmsPfxN1jOhM+0u2xU984u2yEagGivz9OBGQ== + dependencies: + chalk "^4.0.0" + jest-diff "^27.4.2" + jest-get-type "^27.4.0" + pretty-format "^27.4.2" + +jest-message-util@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.4.2.tgz#07f3f1bf207d69cf798ce830cc57f1a849f99388" + integrity sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.4.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.4" + pretty-format "^27.4.2" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock-extended@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-2.0.4.tgz#2bb430ba0adb9e10ea6a68d08731f2129330c8fe" + integrity sha512-MgL3B3GjURQFjjPGqbCANydA5BFNPygv0mYp4Tjfxohh9MWwxxX8Eq2p6ncCt/Vt+RAnaLlDaI7gwrDRD7Pt9A== + dependencies: + ts-essentials "^7.0.3" + +jest-mock@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.4.2.tgz#184ff197a25491bfe4570c286daa5d62eb760b88" + integrity sha512-PDDPuyhoukk20JrQKeofK12hqtSka7mWH0QQuxSNgrdiPsrnYYLS6wbzu/HDlxZRzji5ylLRULeuI/vmZZDrYA== + dependencies: + "@jest/types" "^27.4.2" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.4.0.tgz#e4c45b52653128843d07ad94aec34393ea14fbca" + integrity sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg== + +jest-resolve-dependencies@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.5.tgz#9398af854bdb12d6a9e5a8a536ee401f889a3ecf" + integrity sha512-elEVvkvRK51y037NshtEkEnukMBWvlPzZHiL847OrIljJ8yIsujD2GXRPqDXC4rEVKbcdsy7W0FxoZb4WmEs7w== + dependencies: + "@jest/types" "^27.4.2" + jest-regex-util "^27.4.0" + jest-snapshot "^27.4.5" + +jest-resolve@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.4.5.tgz#8dc44f5065fb8d58944c20f932cb7b9fe9760cca" + integrity sha512-xU3z1BuOz/hUhVUL+918KqUgK+skqOuUsAi7A+iwoUldK6/+PW+utK8l8cxIWT9AW7IAhGNXjSAh1UYmjULZZw== + dependencies: + "@jest/types" "^27.4.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^27.4.5" + jest-pnp-resolver "^1.2.2" + jest-util "^27.4.2" + jest-validate "^27.4.2" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.4.5.tgz#daba2ba71c8f34137dc7ac45616add35370a681e" + integrity sha512-/irauncTfmY1WkTaRQGRWcyQLzK1g98GYG/8QvIPviHgO1Fqz1JYeEIsSfF+9mc/UTA6S+IIHFgKyvUrtiBIZg== + dependencies: + "@jest/console" "^27.4.2" + "@jest/environment" "^27.4.4" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.5" + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-docblock "^27.4.0" + jest-environment-jsdom "^27.4.4" + jest-environment-node "^27.4.4" + jest-haste-map "^27.4.5" + jest-leak-detector "^27.4.2" + jest-message-util "^27.4.2" + jest-resolve "^27.4.5" + jest-runtime "^27.4.5" + jest-util "^27.4.2" + jest-worker "^27.4.5" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.4.5.tgz#97703ad2a1799d4f50ab59049bd21a9ceaed2813" + integrity sha512-CIYqwuJQXHQtPd/idgrx4zgJ6iCb6uBjQq1RSAGQrw2S8XifDmoM1Ot8NRd80ooAm+ZNdHVwsktIMGlA1F1FAQ== + dependencies: + "@jest/console" "^27.4.2" + "@jest/environment" "^27.4.4" + "@jest/globals" "^27.4.4" + "@jest/source-map" "^27.4.0" + "@jest/test-result" "^27.4.2" + "@jest/transform" "^27.4.5" + "@jest/types" "^27.4.2" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-haste-map "^27.4.5" + jest-message-util "^27.4.2" + jest-mock "^27.4.2" + jest-regex-util "^27.4.0" + jest-resolve "^27.4.5" + jest-snapshot "^27.4.5" + jest-util "^27.4.2" + jest-validate "^27.4.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^16.2.0" + +jest-serializer@^27.4.0: + version "27.4.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.4.0.tgz#34866586e1cae2388b7d12ffa2c7819edef5958a" + integrity sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.4.5.tgz#2ea909b20aac0fe62504bc161331f730b8a7ecc7" + integrity sha512-eCi/iM1YJFrJWiT9de4+RpWWWBqsHiYxFG9V9o/n0WXs6GpW4lUt4FAHAgFPTLPqCUVzrMQmSmTZSgQzwqR7IQ== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/parser" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.4.5" + "@jest/types" "^27.4.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.4.2" + graceful-fs "^4.2.4" + jest-diff "^27.4.2" + jest-get-type "^27.4.0" + jest-haste-map "^27.4.5" + jest-matcher-utils "^27.4.2" + jest-message-util "^27.4.2" + jest-resolve "^27.4.5" + jest-util "^27.4.2" + natural-compare "^1.4.0" + pretty-format "^27.4.2" + semver "^7.3.2" + +jest-util@^27.0.0, jest-util@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621" + integrity sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA== + dependencies: + "@jest/types" "^27.4.2" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.4" + picomatch "^2.2.3" + +jest-validate@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.4.2.tgz#eecfcc1b1c9429aa007da08a2bae4e32a81bbbc3" + integrity sha512-hWYsSUej+Fs8ZhOm5vhWzwSLmVaPAxRy+Mr+z5MzeaHm9AxUpXdoVMEW4R86y5gOobVfBsMFLk4Rb+QkiEpx1A== + dependencies: + "@jest/types" "^27.4.2" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.4.0" + leven "^3.1.0" + pretty-format "^27.4.2" + +jest-watcher@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.4.2.tgz#c9037edfd80354c9fe90de4b6f8b6e2b8e736744" + integrity sha512-NJvMVyyBeXfDezhWzUOCOYZrUmkSCiatpjpm+nFUid74OZEHk6aMLrZAukIiFDwdbqp6mTM6Ui1w4oc+8EobQg== + dependencies: + "@jest/test-result" "^27.4.2" + "@jest/types" "^27.4.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.4.2" + string-length "^4.0.1" + +jest-worker@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242" + integrity sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.4.5: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.4.5.tgz#66e45acba44137fac26be9d3cc5bb031e136dc0f" + integrity sha512-uT5MiVN3Jppt314kidCk47MYIRilJjA/l2mxwiuzzxGUeJIvA8/pDaJOAX5KWvjAo7SCydcW0/4WEtgbLMiJkg== + dependencies: + "@jest/core" "^27.4.5" + import-local "^3.0.2" + jest-cli "^27.4.5" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -756,6 +2281,44 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -776,6 +2339,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json5@2.x, json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + keyv@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.4.tgz#f040b236ea2b06ed15ed86fbef8407e1a1c8e376" @@ -783,6 +2353,16 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -791,6 +2371,26 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -801,6 +2401,11 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= +lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -813,11 +2418,38 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-error@^1.1.1: +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + mime-db@1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" @@ -830,6 +2462,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.51.0" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -847,6 +2484,11 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -864,11 +2506,38 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -876,6 +2545,25 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -893,6 +2581,25 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -900,21 +2607,73 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +pirates@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" + integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +pretty-format@^27.0.0, pretty-format@^27.4.2: + version "27.4.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.2.tgz#e4ce92ad66c3888423d332b40477c87d1dac1fb8" + integrity sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw== + dependencies: + "@jest/types" "^27.4.2" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + prism-media@^1.2.9: version "1.3.2" resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.2.tgz#a1f04423ec15d22f3d62b1987b6a25dc49aad13b" @@ -925,6 +2684,19 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -933,7 +2705,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -951,11 +2723,21 @@ random-bunny@^2.0.0: glob-parent "^6.0.0" got "^11.8.3" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + regexpp@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" @@ -966,11 +2748,36 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + responselike@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" @@ -978,20 +2785,42 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -semver@^7.2.1: +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +semver@7.x, semver@^7.2.1, semver@^7.3.2: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -1009,6 +2838,21 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.6" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" + integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -1018,12 +2862,50 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +source-map-support@^0.5.6: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -string-width@^4.2.3: +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1039,6 +2921,16 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -1051,13 +2943,33 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + table@^6.0.9: version "6.7.3" resolved "https://registry.yarnpkg.com/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" @@ -1069,16 +2981,90 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-essentials@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + +ts-jest@^27.1.2: + version "27.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.2.tgz#5991d6eb3fd8e1a8d4b8f6de3ec0a3cc567f3151" + integrity sha512-eSOiJOWq6Hhs6Khzk5wKC5sgWIXgXqOCiIl1+3lfnearu58Hj4QpE5tUhQcA3xtZrELbcvAGCsd6HB8OsaVaTA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + ts-node@^10.4.0: version "10.4.0" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" @@ -1109,16 +3095,45 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typescript@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -1131,19 +3146,63 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vylbot-core@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vylbot-core/-/vylbot-core-2.0.3.tgz#e7d844bc54e72b14f06f14b63866854e3b846d55" - integrity sha512-RxABRcwhVIVZcIssVP8yosLlUXzcdrNLiYJzcvkAIz4KeHfoARcxjsz2vG2x9GIzeFpfn/hjrPvjFC5lN6zclw== +v8-to-istanbul@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" + integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA== dependencies: - discord.js "^12.3.1" - dotenv "^10.0.0" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -1152,6 +3211,15 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -1159,26 +3227,78 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^7.4.4: - version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" - integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.4.4, ws@^7.4.6: + version "7.5.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + 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" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From f61c4c728a8e0c1e872256057526b99a90454c36 Mon Sep 17 00:00:00 2001 From: Vylpes <ethan@vylpes.com> Date: Sun, 30 Jan 2022 17:03:36 +0000 Subject: [PATCH 25/28] Feature/12 create tests (#102) * Fix tests * Update coverage * Remove unrequired mock files * Add about command test * Update about tests * Ban command tests * eval command tests * Start help command tests * Add help command tests * Add kick command tests * Mute command tests * Poll command tests * Add role command tests Signed-off-by: Ethan Lane <ethan@vylpes.com> * Add rules command tests * Add unmute command tests * Add warn command tests * Add MemberEvents tests * Add GuildMemberUpdate tests Signed-off-by: Ethan Lane <ethan@vylpes.com> * Add MessageEvents tests * Add StringTools test Signed-off-by: Ethan Lane <ethan@vylpes.com> * Add embed tests Signed-off-by: Ethan Lane <ethan@vylpes.com> * Add GitHub Actions Signed-off-by: Ethan Lane <ethan@vylpes.com> * Move to tslint Signed-off-by: Ethan Lane <ethan@vylpes.com> * Remove tslint Signed-off-by: Ethan Lane <ethan@vylpes.com> * Remove linting script Signed-off-by: Ethan Lane <ethan@vylpes.com> --- .eslintrc | 49 -- .github/workflows/testing.yml | 27 + .gitignore | 3 +- jest.config.js | 1 + jest.setup.js | 3 + package.json | 4 +- src/client/client.ts | 8 + src/commands/about.ts | 8 +- src/commands/ban.ts | 28 +- src/commands/clear.ts | 19 +- src/commands/eval.ts | 18 +- src/commands/help.ts | 49 +- src/commands/kick.ts | 31 +- src/commands/mute.ts | 37 +- src/commands/poll.ts | 14 +- src/commands/role.ts | 49 +- src/commands/rules.ts | 16 +- src/commands/unmute.ts | 37 +- src/commands/warn.ts | 29 +- src/contracts/ICommandReturnContext.ts | 7 + src/contracts/IEventReturnContext.ts | 6 + src/events/MemberEvents.ts | 19 +- src/events/MemberEvents/GuildMemberUpdate.ts | 25 +- src/events/MessageEvents.ts | 51 +- src/helpers/embeds/ErrorEmbed.ts | 6 +- src/helpers/embeds/EventEmbed.ts | 12 +- src/helpers/embeds/LogEmbed.ts | 16 +- src/helpers/embeds/PublicEmbed.ts | 8 +- tests/__mocks/commands/noCategory.ts | 7 - tests/__mocks/commands/normal.ts | 8 - tests/__mocks/commands/roles.ts | 8 - tests/__mocks/events/normal.ts | 5 - tests/_mocks/commands/mockCmd.ts | 10 + tests/_mocks/rules/rules.json | 13 + tests/client/client.test.ts | 28 + tests/client/events.test.ts | 72 +- tests/client/util.test.ts | 275 +++---- tests/commands/about.test.ts | 152 ++++ tests/commands/ban.test.ts | 566 +++++++++++++++ tests/commands/clear.test.ts | 178 +++++ tests/commands/eval.test.ts | 136 ++++ tests/commands/help.test.ts | 267 +++++++ tests/commands/kick.test.ts | 448 ++++++++++++ tests/commands/mute.test.ts | 675 ++++++++++++++++++ tests/commands/poll.test.ts | 262 +++++++ tests/commands/role.test.ts | 411 +++++++++++ tests/commands/rules.test.ts | 106 +++ tests/commands/unmute.test.ts | 673 +++++++++++++++++ tests/commands/warn.test.ts | 485 +++++++++++++ tests/events/MemberEvents.test.ts | 164 +++++ .../MemberEvents/GuildMemberUpdate.test.ts | 235 ++++++ tests/events/MessageEvents.test.ts | 648 +++++++++++++++++ tests/helpers/StringTools.test.ts | 11 + tests/helpers/embeds/ErrorEmbed.test.ts | 57 ++ tests/helpers/embeds/EventEmbed.test.ts | 222 ++++++ tests/helpers/embeds/LogEmbed.test.ts | 331 +++++++++ tests/helpers/embeds/PublicEmbed.test.ts | 67 ++ yarn.lock | 482 +------------ 58 files changed, 6749 insertions(+), 833 deletions(-) delete mode 100644 .eslintrc create mode 100644 .github/workflows/testing.yml create mode 100644 jest.setup.js create mode 100644 src/contracts/ICommandReturnContext.ts create mode 100644 src/contracts/IEventReturnContext.ts delete mode 100644 tests/__mocks/commands/noCategory.ts delete mode 100644 tests/__mocks/commands/normal.ts delete mode 100644 tests/__mocks/commands/roles.ts delete mode 100644 tests/__mocks/events/normal.ts create mode 100644 tests/_mocks/commands/mockCmd.ts create mode 100644 tests/_mocks/rules/rules.json create mode 100644 tests/commands/about.test.ts create mode 100644 tests/commands/ban.test.ts create mode 100644 tests/commands/clear.test.ts create mode 100644 tests/commands/eval.test.ts create mode 100644 tests/commands/help.test.ts create mode 100644 tests/commands/kick.test.ts create mode 100644 tests/commands/mute.test.ts create mode 100644 tests/commands/poll.test.ts create mode 100644 tests/commands/role.test.ts create mode 100644 tests/commands/rules.test.ts create mode 100644 tests/commands/unmute.test.ts create mode 100644 tests/commands/warn.test.ts create mode 100644 tests/events/MemberEvents.test.ts create mode 100644 tests/events/MemberEvents/GuildMemberUpdate.test.ts create mode 100644 tests/events/MessageEvents.test.ts create mode 100644 tests/helpers/StringTools.test.ts create mode 100644 tests/helpers/embeds/ErrorEmbed.test.ts create mode 100644 tests/helpers/embeds/EventEmbed.test.ts create mode 100644 tests/helpers/embeds/LogEmbed.test.ts create mode 100644 tests/helpers/embeds/PublicEmbed.test.ts diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index c2abdca..0000000 --- a/.eslintrc +++ /dev/null @@ -1,49 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 6 - }, - "extends": [ - "eslint:recommended" - ], - "rules": { - "camelcase": "error", - "brace-style": [ - "error", - "1tbs" - ], - "comma-dangle": [ - "error", - "never" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "comma-style": [ - "error", - "last" - ], - "arrow-body-style": [ - "error", - "as-needed" - ], - "arrow-parens": [ - "error", - "as-needed" - ], - "arrow-spacing": "error", - "no-var": "error", - "prefer-template": "error", - "prefer-const": "error" - }, - "globals": { - "exports": "writable", - "module": "writable", - "require": "writable", - "process": "writable", - "console": "writable" - } -} diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..6654e30 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,27 @@ +name: Testing + +on: + pull_request: + branches: + - main + - develop + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x, 14.x, 16.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install + - run: yarn build + - run: yarn test --coverage + diff --git a/.gitignore b/.gitignore index 83f9014..707ff92 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,5 @@ dist # TernJS port file .tern-port -config.json \ No newline at end of file +config.json +.DS_Store \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 5839600..641ea7d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,5 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', + setupFiles: ["./jest.setup.js"] }; \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..d583d1a --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,3 @@ +jest.setTimeout(1 * 1000); // 1 second +jest.resetModules(); +jest.resetAllMocks(); \ No newline at end of file diff --git a/package.json b/package.json index 746c568..27c731c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "typings": "./dist", "scripts": { "build": "tsc", - "start": "ts-node ./src/vylbot", + "start": "node ./dist/vylbot", "test": "jest" }, "repository": { @@ -29,8 +29,6 @@ }, "devDependencies": { "@types/node": "^16.11.10", - "eslint": "^7.17.0", - "ts-node": "^10.4.0", "typescript": "^4.5.2" } } diff --git a/src/client/client.ts b/src/client/client.ts index f6c992b..c52734c 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -15,6 +15,14 @@ export class CoreClient extends Client { private _events: Events; private _util: Util; + public get commandItems(): ICommandItem[] { + return this._commandItems; + } + + public get eventItems(): IEventItem[] { + return this._eventItems; + } + constructor() { super(); dotenv.config(); diff --git a/src/commands/about.ts b/src/commands/about.ts index 7b75cf8..015c7e5 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -1,4 +1,5 @@ import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; @@ -8,12 +9,17 @@ export default class About extends Command { super._category = "General"; } - public override execute(context: ICommandContext) { + public override execute(context: ICommandContext): ICommandReturnContext { const embed = new PublicEmbed(context, "About", "") .addField("Version", process.env.BOT_VER) .addField("Author", process.env.BOT_AUTHOR) .addField("Date", process.env.BOT_DATE); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } } \ No newline at end of file diff --git a/src/commands/ban.ts b/src/commands/ban.ts index 30a17fe..fddac6c 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -4,6 +4,7 @@ import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; export default class Ban extends Command { constructor() { @@ -15,13 +16,16 @@ export default class Ban extends Command { ]; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise<ICommandReturnContext> { const targetUser = context.message.mentions.users.first(); if (!targetUser) { const embed = new ErrorEmbed(context, "User does not exist"); embed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [embed], + }; } const targetMember = context.message.guild?.member(targetUser); @@ -29,7 +33,10 @@ export default class Ban extends Command { if (!targetMember) { const embed = new ErrorEmbed(context, "User is not in this server"); embed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [embed], + }; } const reasonArgs = context.args; @@ -38,13 +45,19 @@ export default class Ban extends Command { const reason = reasonArgs.join(" "); if (!context.message.guild?.available) { - return; + return { + commandContext: context, + embeds: [], + }; } if (!targetMember.bannable) { const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); embed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [embed], + }; } const logEmbed = new LogEmbed(context, "Member Banned"); @@ -58,5 +71,10 @@ export default class Ban extends Command { logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [logEmbed, publicEmbed], + }; } } \ No newline at end of file diff --git a/src/commands/clear.ts b/src/commands/clear.ts index cf940a7..4f8f893 100644 --- a/src/commands/clear.ts +++ b/src/commands/clear.ts @@ -3,6 +3,7 @@ import { TextChannel } from "discord.js"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; export default class Clear extends Command { constructor() { @@ -14,11 +15,15 @@ export default class Clear extends Command { ]; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise<ICommandReturnContext> { if (context.args.length == 0) { const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const totalToClear = Number.parseInt(context.args[0]); @@ -26,12 +31,20 @@ export default class Clear extends Command { if (!totalToClear || totalToClear <= 0 || totalToClear > 100) { const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100"); errorEmbed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [errorEmbed] + }; } await (context.message.channel as TextChannel).bulkDelete(totalToClear); const embed = new PublicEmbed(context, "", `${totalToClear} message(s) were removed`); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } } \ No newline at end of file diff --git a/src/commands/eval.ts b/src/commands/eval.ts index 29e940c..222367d 100644 --- a/src/commands/eval.ts +++ b/src/commands/eval.ts @@ -1,4 +1,5 @@ import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; @@ -10,9 +11,12 @@ export default class Evaluate extends Command { super._category = "Owner"; } - public override execute(context: ICommandContext) { + public override execute(context: ICommandContext): ICommandReturnContext { if (context.message.author.id != process.env.BOT_OWNERID) { - return; + return { + commandContext: context, + embeds: [] + }; } const stmt = context.args; @@ -24,10 +28,20 @@ export default class Evaluate extends Command { const embed = new PublicEmbed(context, "", result); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } catch (err: any) { const errorEmbed = new ErrorEmbed(context, err); errorEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [errorEmbed] + }; } } } \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts index 6b0d148..aaf8f7e 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -3,9 +3,10 @@ import { ICommandContext } from "../contracts/ICommandContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import StringTools from "../helpers/StringTools"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import { Command } from "../type/command"; -interface ICommandData { +export interface ICommandData { Exists: boolean; Name?: string; Category?: string; @@ -19,17 +20,17 @@ export default class Help extends Command { super._category = "General"; } - public override execute(context: ICommandContext) { + public override execute(context: ICommandContext): ICommandReturnContext { if (context.args.length == 0) { - this.SendAll(context); + return this.SendAll(context); } else { - this.SendSingle(context); + return this.SendSingle(context); } } - private SendAll(context: ICommandContext) { + public SendAll(context: ICommandContext): ICommandReturnContext { const allCommands = this.GetAllCommandData(); - const cateogries = this.DetermineCategories(allCommands); + const cateogries = [...new Set(allCommands.map(x => x.Category!))];; const embed = new PublicEmbed(context, "Commands", ""); @@ -40,15 +41,24 @@ export default class Help extends Command { }); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [ embed ] + }; } - private SendSingle(context: ICommandContext) { + public SendSingle(context: ICommandContext): ICommandReturnContext { const command = this.GetCommandData(context.args[0]); if (!command.Exists) { const errorEmbed = new ErrorEmbed(context, "Command does not exist"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [ errorEmbed ] + }; } const embed = new PublicEmbed(context, StringTools.Capitalise(command.Name!), ""); @@ -56,9 +66,14 @@ export default class Help extends Command { embed.addField("Required Roles", StringTools.Capitalise(command.Roles!.join(", ")) || "*none*"); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [ embed ] + }; } - private GetAllCommandData(): ICommandData[] { + public GetAllCommandData(): ICommandData[] { const result: ICommandData[] = []; const folder = process.env.FOLDERS_COMMANDS!; @@ -82,7 +97,7 @@ export default class Help extends Command { return result; } - private GetCommandData(name: string): ICommandData { + public GetCommandData(name: string): ICommandData { const folder = process.env.FOLDERS_COMMANDS!; const path = `${process.cwd()}/${folder}/${name}.ts`; @@ -104,16 +119,4 @@ export default class Help extends Command { return data; } - - private DetermineCategories(commands: ICommandData[]): string[] { - const result: string[] = []; - - commands.forEach(cmd => { - if (!result.includes(cmd.Category!)) { - result.push(cmd.Category!); - } - }); - - return result; - } -} \ No newline at end of file +} diff --git a/src/commands/kick.ts b/src/commands/kick.ts index fc3dd41..a98c729 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -1,5 +1,6 @@ import ErrorMessages from "../constants/ErrorMessages"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -15,13 +16,17 @@ export default class Kick extends Command { ]; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise<ICommandReturnContext> { const targetUser = context.message.mentions.users.first(); if (!targetUser) { const embed = new ErrorEmbed(context, "User does not exist"); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const targetMember = context.message.guild?.member(targetUser); @@ -29,7 +34,11 @@ export default class Kick extends Command { if (!targetMember) { const embed = new ErrorEmbed(context, "User is not in this server"); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const reasonArgs = context.args; @@ -38,13 +47,20 @@ export default class Kick extends Command { const reason = reasonArgs.join(" "); if (!context.message.guild?.available) { - return; + return { + commandContext: context, + embeds: [] + }; } if (!targetMember.kickable) { const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const logEmbed = new LogEmbed(context, "Member Kicked"); @@ -58,5 +74,10 @@ export default class Kick extends Command { logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [logEmbed, publicEmbed] + }; } } \ No newline at end of file diff --git a/src/commands/mute.ts b/src/commands/mute.ts index f18616f..dd0f81a 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -1,5 +1,6 @@ import ErrorMessages from "../constants/ErrorMessages"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -15,13 +16,17 @@ export default class Mute extends Command { ]; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise<ICommandReturnContext> { const targetUser = context.message.mentions.users.first(); if (!targetUser) { const embed = new ErrorEmbed(context, "User does not exist"); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const targetMember = context.message.guild?.member(targetUser); @@ -29,7 +34,11 @@ export default class Mute extends Command { if (!targetMember) { const embed = new ErrorEmbed(context, "User is not in this server"); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const reasonArgs = context.args; @@ -38,13 +47,20 @@ export default class Mute extends Command { const reason = reasonArgs.join(" "); if (!context.message.guild?.available) { - return; + return { + commandContext: context, + embeds: [] + }; } if (!targetMember.manageable) { const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const logEmbed = new LogEmbed(context, "Member Muted"); @@ -60,12 +76,21 @@ export default class Mute extends Command { if (!mutedRole) { const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } await targetMember.roles.add(mutedRole, reason); logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [logEmbed, publicEmbed] + }; } } \ No newline at end of file diff --git a/src/commands/poll.ts b/src/commands/poll.ts index fb3ea55..c9233be 100644 --- a/src/commands/poll.ts +++ b/src/commands/poll.ts @@ -1,4 +1,5 @@ import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; @@ -10,14 +11,18 @@ export default class Poll extends Command { super._category = "General"; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise<ICommandReturnContext> { const argsJoined = context.args.join(" "); const argsSplit = argsJoined.split(";"); if (argsSplit.length < 3 || argsSplit.length > 10) { const errorEmbed = new ErrorEmbed(context, "Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const title = argsSplit[0]; @@ -53,5 +58,10 @@ export default class Poll extends Command { if (context.message.deletable) { await context.message.delete({ reason: "Poll command" }); } + + return { + commandContext: context, + embeds: [embed] + }; } } \ No newline at end of file diff --git a/src/commands/role.ts b/src/commands/role.ts index a5992f7..e9e7475 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -3,6 +3,7 @@ import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Role as DiscordRole } from "discord.js"; import { Command } from "../type/command"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; export default class Role extends Command { constructor() { @@ -11,30 +12,39 @@ export default class Role extends Command { super._category = "General"; } - public override execute(context: ICommandContext) { + public override async execute(context: ICommandContext) { const roles = process.env.COMMANDS_ROLE_ROLES!.split(','); if (context.args.length == 0) { this.SendRolesList(context, roles); } else { - this.ToggleRole(context, roles); + await this.ToggleRole(context, roles); } } - private SendRolesList(context: ICommandContext, roles: String[]) { + public SendRolesList(context: ICommandContext, roles: String[]): ICommandReturnContext { const description = `Do ${process.env.BOT_PREFIX}role <role> to get the role!\n${roles.join('\n')}`; const embed = new PublicEmbed(context, "Roles", description); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } - private ToggleRole(context: ICommandContext, roles: String[]) { + public async ToggleRole(context: ICommandContext, roles: String[]): Promise<ICommandReturnContext> { const requestedRole = context.args[0]; if (!roles.includes(requestedRole)) { const errorEmbed = new ErrorEmbed(context, "This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const assignRole = context.message.guild?.roles.cache.find(x => x.name == requestedRole); @@ -42,29 +52,48 @@ export default class Role extends Command { if (!assignRole) { const errorEmbed = new ErrorEmbed(context, "The current server doesn't have this role. Please contact the server's moderators"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const role = context.message.member?.roles.cache.find(x => x.name == requestedRole) if (!role) { - this.AddRole(context, assignRole); + await this.AddRole(context, assignRole); } else { - this.RemoveRole(context, assignRole); + await this.RemoveRole(context, assignRole); } + + return { + commandContext: context, + embeds: [] + }; } - private async AddRole(context: ICommandContext, role: DiscordRole) { + public async AddRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> { await context.message.member?.roles.add(role); const embed = new PublicEmbed(context, "", `Gave role: ${role.name}`); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } - private async RemoveRole(context: ICommandContext, role: DiscordRole) { + public async RemoveRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> { await context.message.member?.roles.remove(role); const embed = new PublicEmbed(context, "", `Removed role: ${role.name}`); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } } \ No newline at end of file diff --git a/src/commands/rules.ts b/src/commands/rules.ts index 8578405..c41f381 100644 --- a/src/commands/rules.ts +++ b/src/commands/rules.ts @@ -1,5 +1,6 @@ import { existsSync, readFileSync } from "fs"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; @@ -21,11 +22,15 @@ export default class Rules extends Command { ]; } - public override execute(context: ICommandContext) { - if (!existsSync(process.env.COMMANDS_RULES_FILE!)) { + public override execute(context: ICommandContext): ICommandReturnContext { + if (!existsSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE!}`)) { const errorEmbed = new ErrorEmbed(context, "Rules file doesn't exist"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const rulesFile = readFileSync(`${process.cwd()}/${process.env.COMMANDS_RULES_FILE}`).toString(); @@ -43,5 +48,10 @@ export default class Rules extends Command { }); embeds.forEach(x => x.SendToCurrentChannel()); + + return { + commandContext: context, + embeds: embeds + }; } } \ No newline at end of file diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts index da870cd..51face6 100644 --- a/src/commands/unmute.ts +++ b/src/commands/unmute.ts @@ -1,5 +1,6 @@ import ErrorMessages from "../constants/ErrorMessages"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -15,13 +16,17 @@ export default class Unmute extends Command { ]; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise<ICommandReturnContext> { const targetUser = context.message.mentions.users.first(); if (!targetUser) { const embed = new ErrorEmbed(context, "User does not exist"); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const targetMember = context.message.guild?.member(targetUser); @@ -29,7 +34,11 @@ export default class Unmute extends Command { if (!targetMember) { const embed = new ErrorEmbed(context, "User is not in this server"); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const reasonArgs = context.args; @@ -38,13 +47,20 @@ export default class Unmute extends Command { const reason = reasonArgs.join(" "); if (!context.message.guild?.available) { - return; + return { + commandContext: context, + embeds: [] + }; } if (!targetMember.manageable) { const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } const logEmbed = new LogEmbed(context, "Member Unmuted"); @@ -60,12 +76,21 @@ export default class Unmute extends Command { if (!mutedRole) { const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound); embed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [embed] + }; } await targetMember.roles.remove(mutedRole, reason); logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [logEmbed, publicEmbed] + }; } } \ No newline at end of file diff --git a/src/commands/warn.ts b/src/commands/warn.ts index 542c12f..b4fc236 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -1,4 +1,5 @@ import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -14,21 +15,29 @@ export default class Warn extends Command { ]; } - public override execute(context: ICommandContext) { + public override execute(context: ICommandContext): ICommandReturnContext { const user = context.message.mentions.users.first(); if (!user) { - const errorEmbed = new ErrorEmbed(context, "Please specify a valid user"); + const errorEmbed = new ErrorEmbed(context, "User does not exist"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const member = context.message.guild?.member(user); if (!member) { - const errorEmbed = new ErrorEmbed(context, "Please specify a valid user"); + const errorEmbed = new ErrorEmbed(context, "User is not in this server"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const reasonArgs = context.args; @@ -37,7 +46,10 @@ export default class Warn extends Command { const reason = reasonArgs.join(" "); if (!context.message.guild?.available) { - return; + return { + commandContext: context, + embeds: [] + }; } const logEmbed = new LogEmbed(context, "Member Warned"); @@ -50,5 +62,10 @@ export default class Warn extends Command { logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [logEmbed, publicEmbed] + }; } } \ No newline at end of file diff --git a/src/contracts/ICommandReturnContext.ts b/src/contracts/ICommandReturnContext.ts new file mode 100644 index 0000000..144e981 --- /dev/null +++ b/src/contracts/ICommandReturnContext.ts @@ -0,0 +1,7 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "./ICommandContext"; + +export default interface ICommandReturnContext { + commandContext: ICommandContext, + embeds: MessageEmbed[] +} \ No newline at end of file diff --git a/src/contracts/IEventReturnContext.ts b/src/contracts/IEventReturnContext.ts new file mode 100644 index 0000000..ccbe56d --- /dev/null +++ b/src/contracts/IEventReturnContext.ts @@ -0,0 +1,6 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "./ICommandContext"; + +export default interface ICommandReturnContext { + embeds: MessageEmbed[] +} \ No newline at end of file diff --git a/src/events/MemberEvents.ts b/src/events/MemberEvents.ts index 2aada07..5bcda27 100644 --- a/src/events/MemberEvents.ts +++ b/src/events/MemberEvents.ts @@ -2,35 +2,48 @@ import { Event } from "../type/event"; import { GuildMember } from "discord.js"; import EventEmbed from "../helpers/embeds/EventEmbed"; import GuildMemberUpdate from "./MemberEvents/GuildMemberUpdate"; +import IEventReturnContext from "../contracts/IEventReturnContext"; export default class MemberEvents extends Event { constructor() { super(); } - public override guildMemberAdd(member: GuildMember) { + public override guildMemberAdd(member: GuildMember): IEventReturnContext { const embed = new EventEmbed(member.guild, "Member Joined"); embed.AddUser("User", member.user, true); embed.addField("Created", member.user.createdAt); embed.setFooter(`Id: ${member.user.id}`); embed.SendToMemberLogsChannel(); + + return { + embeds: [embed] + }; } - public override guildMemberRemove(member: GuildMember) { + public override guildMemberRemove(member: GuildMember): IEventReturnContext { const embed = new EventEmbed(member.guild, "Member Left"); embed.AddUser("User", member.user, true); embed.addField("Joined", member.joinedAt); embed.setFooter(`Id: ${member.user.id}`); embed.SendToMemberLogsChannel(); + + return { + embeds: [embed] + }; } - public override guildMemberUpdate(oldMember: GuildMember, newMember: GuildMember) { + public override guildMemberUpdate(oldMember: GuildMember, newMember: GuildMember): IEventReturnContext { const handler = new GuildMemberUpdate(oldMember, newMember); if (oldMember.nickname != newMember.nickname) { // Nickname change handler.NicknameChanged(); } + + return { + embeds: [] + }; } } \ No newline at end of file diff --git a/src/events/MemberEvents/GuildMemberUpdate.ts b/src/events/MemberEvents/GuildMemberUpdate.ts index 2f15a16..984721a 100644 --- a/src/events/MemberEvents/GuildMemberUpdate.ts +++ b/src/events/MemberEvents/GuildMemberUpdate.ts @@ -1,25 +1,30 @@ import { GuildMember } from "discord.js"; +import IEventReturnContext from "../../contracts/IEventReturnContext"; import EventEmbed from "../../helpers/embeds/EventEmbed"; export default class GuildMemberUpdate { - private _oldMember: GuildMember; - private _newMember: GuildMember; + public oldMember: GuildMember; + public newMember: GuildMember; constructor(oldMember: GuildMember, newMember: GuildMember) { - this._oldMember = oldMember; - this._newMember = newMember; + this.oldMember = oldMember; + this.newMember = newMember; } - public NicknameChanged() { - const oldNickname = this._oldMember.nickname || "*none*"; - const newNickname = this._newMember.nickname || "*none*"; + public NicknameChanged(): IEventReturnContext { + const oldNickname = this.oldMember.nickname || "*none*"; + const newNickname = this.newMember.nickname || "*none*"; - const embed = new EventEmbed(this._newMember.guild, "Nickname Changed"); - embed.AddUser("User", this._newMember.user, true); + const embed = new EventEmbed(this.newMember.guild, "Nickname Changed"); + embed.AddUser("User", this.newMember.user, true); embed.addField("Before", oldNickname, true); embed.addField("After", newNickname, true); - embed.setFooter(`Id: ${this._newMember.user.id}`); + embed.setFooter(`Id: ${this.newMember.user.id}`); embed.SendToMemberLogsChannel(); + + return { + embeds: [embed] + }; } } \ No newline at end of file diff --git a/src/events/MessageEvents.ts b/src/events/MessageEvents.ts index 0972a23..18f3704 100644 --- a/src/events/MessageEvents.ts +++ b/src/events/MessageEvents.ts @@ -1,29 +1,60 @@ import { Event } from "../type/event"; import { Message } from "discord.js"; import EventEmbed from "../helpers/embeds/EventEmbed"; +import IEventReturnContext from "../contracts/IEventReturnContext"; export default class MessageEvents extends Event { constructor() { super(); } - public override messageDelete(message: Message) { - if (!message.guild) return; - if (message.author.bot) return; + public override messageDelete(message: Message): IEventReturnContext { + if (!message.guild) { + return { + embeds: [] + }; + } + + if (message.author.bot) { + return { + embeds: [] + }; + } const embed = new EventEmbed(message.guild, "Message Deleted"); embed.AddUser("User", message.author, true); embed.addField("Channel", message.channel, true); embed.addField("Content", `\`\`\`${message.content || "*none*"}\`\`\``); - embed.addField("Attachments", `\`\`\`${message.attachments.map(x => x.url).join("\n")}`); + + if (message.attachments.size > 0) { + embed.addField("Attachments", `\`\`\`${message.attachments.map(x => x.url).join("\n")}\`\`\``); + } embed.SendToMessageLogsChannel(); + + return { + embeds: [embed] + }; } - public override messageUpdate(oldMessage: Message, newMessage: Message) { - if (!newMessage.guild) return; - if (newMessage.author.bot) return; - if (oldMessage.content == newMessage.content) return; + public override messageUpdate(oldMessage: Message, newMessage: Message): IEventReturnContext { + if (!newMessage.guild){ + return { + embeds: [] + }; + } + + if (newMessage.author.bot) { + return { + embeds: [] + }; + } + + if (oldMessage.content == newMessage.content) { + return { + embeds: [] + }; + } const embed = new EventEmbed(newMessage.guild, "Message Edited"); embed.AddUser("User", newMessage.author, true); @@ -32,5 +63,9 @@ export default class MessageEvents extends Event { embed.addField("After", `\`\`\`${newMessage.content || "*none*"}\`\`\``); embed.SendToMessageLogsChannel(); + + return { + embeds: [embed] + }; } } \ No newline at end of file diff --git a/src/helpers/embeds/ErrorEmbed.ts b/src/helpers/embeds/ErrorEmbed.ts index ded3d2d..6b2c06d 100644 --- a/src/helpers/embeds/ErrorEmbed.ts +++ b/src/helpers/embeds/ErrorEmbed.ts @@ -2,7 +2,7 @@ import { MessageEmbed } from "discord.js"; import { ICommandContext } from "../../contracts/ICommandContext"; export default class ErrorEmbed extends MessageEmbed { - private _context: ICommandContext; + public context: ICommandContext; constructor(context: ICommandContext, message: String) { super(); @@ -10,10 +10,10 @@ export default class ErrorEmbed extends MessageEmbed { super.setColor(process.env.EMBED_COLOUR_ERROR!); super.setDescription(message); - this._context = context; + this.context = context; } public SendToCurrentChannel() { - this._context.message.channel.send(this); + this.context.message.channel.send(this); } } \ No newline at end of file diff --git a/src/helpers/embeds/EventEmbed.ts b/src/helpers/embeds/EventEmbed.ts index 8b4d364..885961b 100644 --- a/src/helpers/embeds/EventEmbed.ts +++ b/src/helpers/embeds/EventEmbed.ts @@ -1,7 +1,7 @@ import { MessageEmbed, TextChannel, User, Guild } from "discord.js"; export default class EventEmbed extends MessageEmbed { - private _guild: Guild; + public guild: Guild; constructor(guild: Guild, title: string) { super(); @@ -9,25 +9,25 @@ export default class EventEmbed extends MessageEmbed { super.setColor(process.env.EMBED_COLOUR!); super.setTitle(title); - this._guild = guild; + this.guild = guild; } // Detail methods public AddUser(title: string, user: User, setThumbnail: boolean = false) { - super.addField(title, `${user} \`${user.tag}\``, true); + this.addField(title, `${user} \`${user.tag}\``, true); if (setThumbnail) { - super.setThumbnail(user.displayAvatarURL()); + this.setThumbnail(user.displayAvatarURL()); } } public AddReason(message: String) { - super.addField("Reason", message || "*none*"); + this.addField("Reason", message || "*none*"); } // Send methods public SendToChannel(name: string) { - const channel = this._guild.channels.cache + const channel = this.guild.channels.cache .find(channel => channel.name == name) as TextChannel; if (!channel) { diff --git a/src/helpers/embeds/LogEmbed.ts b/src/helpers/embeds/LogEmbed.ts index 00eb17a..f5e2e36 100644 --- a/src/helpers/embeds/LogEmbed.ts +++ b/src/helpers/embeds/LogEmbed.ts @@ -4,7 +4,7 @@ import { ICommandContext } from "../../contracts/ICommandContext"; import ErrorEmbed from "./ErrorEmbed"; export default class LogEmbed extends MessageEmbed { - private _context: ICommandContext; + public context: ICommandContext; constructor(context: ICommandContext, title: string) { super(); @@ -12,33 +12,33 @@ export default class LogEmbed extends MessageEmbed { super.setColor(process.env.EMBED_COLOUR!); super.setTitle(title); - this._context = context; + this.context = context; } // Detail methods public AddUser(title: string, user: User, setThumbnail: boolean = false) { - super.addField(title, `${user} \`${user.tag}\``, true); + this.addField(title, `${user} \`${user.tag}\``, true); if (setThumbnail) { - super.setThumbnail(user.displayAvatarURL()); + this.setThumbnail(user.displayAvatarURL()); } } public AddReason(message: String) { - super.addField("Reason", message || "*none*"); + this.addField("Reason", message || "*none*"); } // Send methods public SendToCurrentChannel() { - this._context.message.channel.send(this); + this.context.message.channel.send(this); } public SendToChannel(name: string) { - const channel = this._context.message.guild?.channels.cache + const channel = this.context.message.guild?.channels.cache .find(channel => channel.name == name) as TextChannel; if (!channel) { - const errorEmbed = new ErrorEmbed(this._context, ErrorMessages.ChannelNotFound); + const errorEmbed = new ErrorEmbed(this.context, ErrorMessages.ChannelNotFound); errorEmbed.SendToCurrentChannel(); return; } diff --git a/src/helpers/embeds/PublicEmbed.ts b/src/helpers/embeds/PublicEmbed.ts index 4a02f17..2bd40cd 100644 --- a/src/helpers/embeds/PublicEmbed.ts +++ b/src/helpers/embeds/PublicEmbed.ts @@ -2,7 +2,7 @@ import { MessageEmbed } from "discord.js"; import { ICommandContext } from "../../contracts/ICommandContext"; export default class PublicEmbed extends MessageEmbed { - private _context: ICommandContext; + public context: ICommandContext; constructor(context: ICommandContext, title: string, description: string) { super(); @@ -11,16 +11,16 @@ export default class PublicEmbed extends MessageEmbed { super.setTitle(title); super.setDescription(description); - this._context = context; + this.context = context; } // Detail methods public AddReason(message: String) { - super.addField("Reason", message || "*none*"); + this.addField("Reason", message || "*none*"); } // Send methods public SendToCurrentChannel() { - this._context.message.channel.send(this); + this.context.message.channel.send(this); } } \ No newline at end of file diff --git a/tests/__mocks/commands/noCategory.ts b/tests/__mocks/commands/noCategory.ts deleted file mode 100644 index e71cd53..0000000 --- a/tests/__mocks/commands/noCategory.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Command } from "../../../src/type/command"; - -export default class noCategory extends Command { - constructor() { - super(); - } -} diff --git a/tests/__mocks/commands/normal.ts b/tests/__mocks/commands/normal.ts deleted file mode 100644 index 3e3a74f..0000000 --- a/tests/__mocks/commands/normal.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Command } from "../../../src/type/command"; - -export default class normal extends Command { - constructor() { - super(); - this._category = "General"; - } -} diff --git a/tests/__mocks/commands/roles.ts b/tests/__mocks/commands/roles.ts deleted file mode 100644 index 1e0321e..0000000 --- a/tests/__mocks/commands/roles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Command } from "../../../src/type/command"; - -export default class roles extends Command { - constructor() { - super(); - this._roles = [ "Moderator" ]; - } -} diff --git a/tests/__mocks/events/normal.ts b/tests/__mocks/events/normal.ts deleted file mode 100644 index 8a85eaa..0000000 --- a/tests/__mocks/events/normal.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Event } from "../../../src/type/event"; - -export class normal extends Event { - public override channelCreate() {} -} \ No newline at end of file diff --git a/tests/_mocks/commands/mockCmd.ts b/tests/_mocks/commands/mockCmd.ts new file mode 100644 index 0000000..16f1947 --- /dev/null +++ b/tests/_mocks/commands/mockCmd.ts @@ -0,0 +1,10 @@ +import { Command } from "../../../src/type/command"; + +export default class MockCmd extends Command { + constructor() { + super(); + + super._category = "General"; + super._roles = ["Moderator"]; + } +} \ No newline at end of file diff --git a/tests/_mocks/rules/rules.json b/tests/_mocks/rules/rules.json new file mode 100644 index 0000000..bcc28b9 --- /dev/null +++ b/tests/_mocks/rules/rules.json @@ -0,0 +1,13 @@ +[ + { + "image": "IMAGEURL" + }, + { + "title": "TITLE 1", + "description": [ + "DESCRIPTION 1A", + "DESCRIPTION 1B" + ], + "footer": "FOOTER 1" + } +] \ No newline at end of file diff --git a/tests/client/client.test.ts b/tests/client/client.test.ts index 712cb41..46879ba 100644 --- a/tests/client/client.test.ts +++ b/tests/client/client.test.ts @@ -4,6 +4,9 @@ import { Client } from "discord.js"; import * as dotenv from "dotenv"; import { Events } from "../../src/client/events"; import { Util } from "../../src/client/util"; +import { Command } from "../../src/type/command"; +import { mock } from "jest-mock-extended"; +import { Event } from "../../src/type/event"; jest.mock("discord.js"); jest.mock("dotenv"); @@ -136,4 +139,29 @@ describe('Start', () => { expect(() => coreClient.start()).toThrow("FOLDERS_EVENTS is not defined in .env"); }); +}); + +describe('RegisterCommand', () => { + test('Expect command added to register', () => { + const cmd = mock<Command>(); + + const client = new CoreClient(); + client.RegisterCommand("test", cmd); + + expect(client.commandItems.length).toBe(1); + expect(client.commandItems[0].Name).toBe("test"); + expect(client.commandItems[0].Command).toBe(cmd); + }); +}); + +describe('RegisterEvent', () => { + test('Expect event added to register', () => { + const evt = mock<Event>(); + + const client = new CoreClient(); + client.RegisterEvent(evt); + + expect(client.eventItems.length).toBe(1); + expect(client.eventItems[0].Event).toBe(evt); + }); }); \ No newline at end of file diff --git a/tests/client/events.test.ts b/tests/client/events.test.ts index 69baac9..50eb557 100644 --- a/tests/client/events.test.ts +++ b/tests/client/events.test.ts @@ -1,7 +1,9 @@ import { Events } from "../../src/client/events"; - -import { Message, Client, TextChannel, Guild, SnowflakeUtil, DMChannel } from "discord.js"; +import { Message } from "discord.js"; import { Util } from "../../src/client/util"; +import ICommandItem from "../../src/contracts/ICommandItem"; +import { Command } from "../../src/type/command"; +import { mock } from "jest-mock-extended"; jest.mock("../../src/client/util"); @@ -27,10 +29,19 @@ describe('OnMessage', () => { }, content: "!test first", } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const events = new Events(); - const result = await events.onMessage(message); + const result = await events.onMessage(message, commands); expect(result.valid).toBeTruthy(); @@ -58,10 +69,19 @@ describe('OnMessage', () => { }, content: "!test first", } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const events = new Events(); - const result = await events.onMessage(message); + const result = await events.onMessage(message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Message was not sent in a guild, ignoring."); @@ -84,10 +104,19 @@ describe('OnMessage', () => { }, content: "!test first", } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const events = new Events(); - const result = await events.onMessage(message); + const result = await events.onMessage(message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Message was sent by a bot, ignoring."); @@ -110,10 +139,19 @@ describe('OnMessage', () => { }, content: "This is a standard message", } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const events = new Events(); - const result = await events.onMessage(message); + const result = await events.onMessage(message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Message was not a command, ignoring."); @@ -136,10 +174,19 @@ describe('OnMessage', () => { }, content: "!", } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const events = new Events(); - const result = await events.onMessage(message); + const result = await events.onMessage(message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Command name was not found"); @@ -162,10 +209,19 @@ describe('OnMessage', () => { }, content: "!test first", } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const events = new Events(); - const result = await events.onMessage(message); + const result = await events.onMessage(message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Command failed"); diff --git a/tests/client/util.test.ts b/tests/client/util.test.ts index fe046e2..6429e24 100644 --- a/tests/client/util.test.ts +++ b/tests/client/util.test.ts @@ -1,7 +1,12 @@ import { Util } from "../../src/client/util"; -import { Client, Guild, Message, Role, SnowflakeUtil, TextChannel, User } from "discord.js"; +import { Client, Message } from "discord.js"; import fs from "fs"; +import { mock } from "jest-mock-extended"; +import { Command } from "../../src/type/command"; +import ICommandItem from "../../src/contracts/ICommandItem"; +import IEventItem from "../../src/contracts/IEventItem"; +import { Event } from "../../src/type/event"; jest.mock("fs"); @@ -17,9 +22,6 @@ describe('LoadCommand', () => { FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -31,12 +33,22 @@ describe('LoadCommand', () => { }, reply: jest.fn(), } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const util = new Util(); - const result = util.loadCommand("normal", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeTruthy(); + expect(cmd.execute).toBeCalled(); }); test('Given Member Is Null, Expect Failed Result', () => { @@ -46,81 +58,27 @@ describe('LoadCommand', () => { FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: null } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const util = new Util(); - const result = util.loadCommand("normal", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Member is not part of message"); - }); - - test('Given Folder Does Not Exist, Expect Failed Result', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(false); - - const message = { - member: { - roles: { - cache: { - find: jest.fn().mockReturnValue(true), - } - }, - }, - reply: jest.fn(), - } as unknown as Message; - - const util = new Util(); - - const result = util.loadCommand("normal", [ "first" ], message); - - expect(result.valid).toBeFalsy(); - expect(result.message).toBe("Command folder does not exist"); - }); - - test('Given File Does Not Exist, Expect Failed Result', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValueOnce(true) - .mockReturnValue(false); - - const message = { - member: { - roles: { - cache: { - find: jest.fn().mockReturnValue(true), - } - }, - }, - reply: jest.fn(), - } as unknown as Message; - - const util = new Util(); - - const result = util.loadCommand("normal", [ "first" ], message); - - expect(result.valid).toBeFalsy(); - expect(result.message).toBe("File does not exist"); + expect(cmd.execute).not.toBeCalled(); }); test('Given User Does Have Role, Expect Successful Result', () => { @@ -130,9 +88,6 @@ describe('LoadCommand', () => { FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -144,12 +99,22 @@ describe('LoadCommand', () => { }, reply: jest.fn(), } as unknown as Message; + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const util = new Util(); - const result = util.loadCommand("roles", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeTruthy(); + expect(cmd.execute).toBeCalled(); }); test('Given User Does Not Have Role, Expect Failed Result', () => { @@ -159,9 +124,6 @@ describe('LoadCommand', () => { FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -173,42 +135,24 @@ describe('LoadCommand', () => { }, reply: jest.fn(), } as unknown as Message; + + const cmd = mock<Command>(); + cmd._roles = [ "Moderator" ]; + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const util = new Util(); - const result = util.loadCommand("roles", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("You require the `Moderator` role to run this command"); - }); - - test('Given Command Category Is Null, Expect Successful Result', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); - - const message = { - member: { - roles: { - cache: { - find: jest.fn().mockReturnValue(true), - } - }, - }, - reply: jest.fn(), - } as unknown as Message; - - const util = new Util(); - - const result = util.loadCommand("noCategory", [ "first" ], message); - - expect(result.valid).toBeTruthy(); + expect(cmd.execute).not.toBeCalled(); }); test('Given command is set to disabled, Expect command to not fire', () => { @@ -217,12 +161,9 @@ describe('LoadCommand', () => { BOT_PREFIX: '!', FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', - COMMANDS_DISABLED: 'normal', + COMMANDS_DISABLED: 'test', COMMANDS_DISABLED_MESSAGE: 'disabled', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -236,14 +177,24 @@ describe('LoadCommand', () => { } as unknown as Message; const messageReply = jest.spyOn(message, 'reply'); + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const util = new Util(); - const result = util.loadCommand("normal", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Command is disabled"); expect(messageReply).toBeCalledWith("disabled"); + expect(cmd.execute).not.toBeCalled(); }); test('Given command COMMANDS_DISABLED_MESSAGE is empty, Expect default message sent', () => { @@ -252,11 +203,8 @@ describe('LoadCommand', () => { BOT_PREFIX: '!', FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', - COMMANDS_DISABLED: 'normal', + COMMANDS_DISABLED: 'test', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -270,14 +218,24 @@ describe('LoadCommand', () => { } as unknown as Message; const messageReply = jest.spyOn(message, 'reply'); + + const cmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const commands: ICommandItem[] = [ commandItem ]; const util = new Util(); - const result = util.loadCommand("normal", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); expect(result.message).toBe("Command is disabled"); expect(messageReply).toBeCalledWith("This command is disabled."); + expect(cmd.execute).not.toBeCalled(); }); test('Given a different command is disabled, Expect command to still fire', () => { @@ -286,11 +244,8 @@ describe('LoadCommand', () => { BOT_PREFIX: '!', FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', - COMMANDS_DISABLED: 'anything', + COMMANDS_DISABLED: 'other', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -302,25 +257,38 @@ describe('LoadCommand', () => { }, reply: jest.fn(), } as unknown as Message; + + const cmd = mock<Command>(); + const otherCmd = mock<Command>(); + + const commandItem: ICommandItem = { + Name: "test", + Command: cmd + }; + + const otherCommandItem: ICommandItem = { + Name: "other", + Command: otherCmd, + } + + const commands: ICommandItem[] = [ commandItem, otherCommandItem ]; const util = new Util(); - const result = util.loadCommand("normal", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeTruthy(); + expect(cmd.execute).toBeCalled(); + expect(otherCmd.execute).not.toBeCalled(); }); - test('Given a different command is disabled with this one, Expect command to not fire', () => { + test('Given command is not found in register, expect command not found error', () => { process.env = { BOT_TOKEN: 'TOKEN', BOT_PREFIX: '!', FOLDERS_COMMANDS: 'commands', FOLDERS_EVENTS: 'events', - COMMANDS_DISABLED: 'normal,anything,', } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); const message = { member: { @@ -332,13 +300,16 @@ describe('LoadCommand', () => { }, reply: jest.fn(), } as unknown as Message; + + const commands: ICommandItem[] = []; const util = new Util(); - const result = util.loadCommand("normal", [ "first" ], message); + const result = util.loadCommand("test", [ "first" ], message, commands); expect(result.valid).toBeFalsy(); - expect(result.message).toBe("Command is disabled"); + expect(result.message).toBe('Command not found'); + expect(message.reply).toBeCalledWith('Command not found'); }); }); @@ -351,17 +322,21 @@ describe('LoadEvents', () => { FOLDERS_EVENTS: 'events', } - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); - fs.readdirSync = jest.fn().mockReturnValue(["normal.ts"]); - const client = { on: jest.fn(), } as unknown as Client; + + const evt = mock<Event>(); + + const eventItem: IEventItem = { + Event: evt + }; + + const eventItems: IEventItem[] = [ eventItem ]; const util = new Util(); - const result = util.loadEvents(client); + const result = util.loadEvents(client, eventItems); const clientOn = jest.spyOn(client, 'on'); @@ -377,45 +352,19 @@ describe('LoadEvents', () => { FOLDERS_EVENTS: 'events', } - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(true); - fs.readdirSync = jest.fn().mockReturnValue(["normal"]); - const client = { on: jest.fn(), } as unknown as Client; + + const eventItems: IEventItem[] = []; const util = new Util(); - const result = util.loadEvents(client); + const result = util.loadEvents(client, eventItems); const clientOn = jest.spyOn(client, 'on'); expect(result.valid).toBeTruthy(); expect(clientOn).toBeCalledTimes(0); }); - - test('Given Event Folder Does Not Exist, Expect Failed Result', () => { - process.env = { - BOT_TOKEN: 'TOKEN', - BOT_PREFIX: '!', - FOLDERS_COMMANDS: 'commands', - FOLDERS_EVENTS: 'events', - } - - process.cwd = jest.fn().mockReturnValue("../../tests/__mocks"); - fs.existsSync = jest.fn().mockReturnValue(false); - fs.readdirSync = jest.fn().mockReturnValue(["normal.ts"]); - - const client = { - on: jest.fn(), - } as unknown as Client; - - const util = new Util(); - - const result = util.loadEvents(client); - - expect(result.valid).toBeFalsy(); - expect(result.message).toBe("Event folder does not exist"); - }); }); diff --git a/tests/commands/about.test.ts b/tests/commands/about.test.ts new file mode 100644 index 0000000..6699cc2 --- /dev/null +++ b/tests/commands/about.test.ts @@ -0,0 +1,152 @@ +import { Message } from "discord.js"; +import { mock } from "jest-mock-extended"; +import About from "../../src/commands/about"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; +import PublicEmbed from "../../src/helpers/embeds/PublicEmbed"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect values set', () => { + const about = new About(); + + expect(about._category).toBe("General"); + }); +}); + +describe('Execute', () => { + test('Expect embed to be made and sent to the current channel', async () => { + process.env = { + BOT_VER: "BOT_VER", + BOT_AUTHOR: "BOT_AUTHOR", + BOT_DATE: "BOT_DATE" + }; + + const message = mock<Message>(); + message.channel.send = jest.fn(); + + const context: ICommandContext = { + name: "about", + args: [], + message: message + }; + + const about = new About(); + + const result = await about.execute(context); + + expect(message.channel.send).toBeCalledTimes(1); + }); + + test('Expect embed send to have values', async () => { + process.env = { + BOT_VER: "BOT_VER", + BOT_AUTHOR: "BOT_AUTHOR", + BOT_DATE: "BOT_DATE" + }; + + const message = mock<Message>(); + message.channel.send = jest.fn(); + + const context: ICommandContext = { + name: "about", + args: [], + message: message + }; + + const about = new About(); + + const result = await about.execute(context); + + expect(result.embeds.length).toBe(1); + + const embed = result.embeds[0]; + + expect(embed.title).toBe('About'); + expect(embed.description).toBe(''); + expect(embed.fields.length).toBe(3); + }); + + test('Expect version field to have values', async () => { + process.env = { + BOT_VER: "BOT_VER", + BOT_AUTHOR: "BOT_AUTHOR", + BOT_DATE: "BOT_DATE" + }; + + const message = mock<Message>(); + message.channel.send = jest.fn(); + + const context: ICommandContext = { + name: "about", + args: [], + message: message + }; + + const about = new About(); + + const result = await about.execute(context); + + const embed = result.embeds[0]; + const field = embed.fields[0]; + + expect(field.name).toBe('Version'); + expect(field.value).toBe('BOT_VER'); + }); + + test('Expect author field to have values', async () => { + process.env = { + BOT_VER: "BOT_VER", + BOT_AUTHOR: "BOT_AUTHOR", + BOT_DATE: "BOT_DATE" + }; + + const message = mock<Message>(); + message.channel.send = jest.fn(); + + const context: ICommandContext = { + name: "about", + args: [], + message: message + }; + + const about = new About(); + + const result = await about.execute(context); + + const embed = result.embeds[0]; + const field = embed.fields[1]; + + expect(field.name).toBe('Author'); + expect(field.value).toBe('BOT_AUTHOR'); + }); + + test('Expect version field to have values', async () => { + process.env = { + BOT_VER: "BOT_VER", + BOT_AUTHOR: "BOT_AUTHOR", + BOT_DATE: "BOT_DATE" + }; + + const message = mock<Message>(); + message.channel.send = jest.fn(); + + const context: ICommandContext = { + name: "about", + args: [], + message: message + }; + + const about = new About(); + + const result = await about.execute(context); + + const embed = result.embeds[0]; + const field = embed.fields[2]; + + expect(field.name).toBe('Date'); + expect(field.value).toBe('BOT_DATE'); + }); +}); \ No newline at end of file diff --git a/tests/commands/ban.test.ts b/tests/commands/ban.test.ts new file mode 100644 index 0000000..6ed09e6 --- /dev/null +++ b/tests/commands/ban.test.ts @@ -0,0 +1,566 @@ +import { GuildMember, Message, TextChannel, User } from "discord.js"; +import Ban from "../../src/commands/ban"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect values to be set', () => { + process.env.ROLES_MODERATOR = 'Moderator'; + + const ban = new Ban(); + + expect(ban._category).toBe('Moderation'); + expect(ban._roles.length).toBe(1); + expect(ban._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect user to be banned', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).toBeCalledTimes(1); + expect(mentionedMember.ban).toBeCalledWith({ reason: 'Test Reason' }); + }); + + test('Given user has permissions, expect embeds to be correct', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(result.embeds.length).toBe(2); + + const logEmbed = result.embeds[0]; + const publicEmbed = result.embeds[1]; + + expect(logEmbed.title).toBe('Member Banned'); + expect(publicEmbed.title).toBe(""); + expect(publicEmbed.description).toBe('[object Object] has been banned'); + expect(logEmbed.fields.length).toBe(3); + expect(publicEmbed.fields.length).toBe(0); + }); + + test('Given user has permission, expect logEmbed fields to be correct', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn().mockReturnValue('URL'), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + const logEmbed = result.embeds[0]; + + const fieldUser = logEmbed.fields[0]; + const fieldModerator = logEmbed.fields[1]; + const fieldReason = logEmbed.fields[2]; + + expect(fieldUser.name).toBe("User"); + expect(fieldUser.value).toBe("[object Object] `USERTAG`"); + expect(logEmbed.thumbnail?.url).toBe("URL"); + + expect(fieldModerator.name).toBe('Moderator'); + expect(fieldModerator.value).toBe('[object Object] `AUTHORTAG`'); + + expect(fieldReason.name).toBe('Reason'); + expect(fieldReason.value).toBe('Test Reason'); + }); + + test('Given user is not mentioned, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(null); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('User does not exist'); + }); + + test('Given member is not in server, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(null); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('User is not in this server'); + }); + + test('Given guild is unavailable, expect return and do nothing', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: false + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).not.toBeCalled(); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given bot cant ban user, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: false, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('Unable to do this action, am I missing permissions?'); + }); +}); \ No newline at end of file diff --git a/tests/commands/clear.test.ts b/tests/commands/clear.test.ts new file mode 100644 index 0000000..ad27d62 --- /dev/null +++ b/tests/commands/clear.test.ts @@ -0,0 +1,178 @@ +import { Message } from "discord.js"; +import Clear from "../../src/commands/clear"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect values to be set', () => { + process.env = { + ROLES_MODERATOR: "Moderator" + }; + + const clear = new Clear(); + + expect(clear._category).toBe('Moderation'); + expect(clear._roles.length).toBe(1); + expect(clear._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given valid arguments, expect messages to be cleared', async () => { + const messageChannelSend = jest.fn(); + const messageChannelBulkDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend, + bulkDelete: messageChannelBulkDelete + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'clear', + args: ['5'], + message: message + }; + + const clear = new Clear(); + const result = await clear.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageChannelBulkDelete).toBeCalledWith(5); + expect(result.embeds.length).toBe(1); + + // PublicEmbed + const publicEmbed = result.embeds[0]; + + expect(publicEmbed.title).toBe(''); + expect(publicEmbed.description).toBe('5 message(s) were removed'); + }); + + test('Given argument is not given, expect error embed to be sent', async () => { + const messageChannelSend = jest.fn(); + const messageChannelBulkDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend, + bulkDelete: messageChannelBulkDelete + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'clear', + args: [], + message: message + }; + + const clear = new Clear(); + const result = await clear.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageChannelBulkDelete).not.toBeCalled(); + expect(result.embeds.length).toBe(1); + + // ErrorEmbed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.title).toBeNull(); + expect(errorEmbed.description).toBe('Please specify an amount between 1 and 100'); + }); + + test('Given argument is not a number, expect error embed to be sent', async () => { + const messageChannelSend = jest.fn(); + const messageChannelBulkDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend, + bulkDelete: messageChannelBulkDelete + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'clear', + args: ['A'], + message: message + }; + + const clear = new Clear(); + const result = await clear.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageChannelBulkDelete).not.toBeCalled(); + expect(result.embeds.length).toBe(1); + + // ErrorEmbed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.title).toBeNull(); + expect(errorEmbed.description).toBe('Please specify an amount between 1 and 100'); + }); + + test('Given argument is less than 1, expect error embed to be sent', async () => { + const messageChannelSend = jest.fn(); + const messageChannelBulkDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend, + bulkDelete: messageChannelBulkDelete + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'clear', + args: ['0'], + message: message + }; + + const clear = new Clear(); + const result = await clear.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageChannelBulkDelete).not.toBeCalled(); + expect(result.embeds.length).toBe(1); + + // ErrorEmbed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.title).toBeNull(); + expect(errorEmbed.description).toBe('Please specify an amount between 1 and 100'); + }); + + test('Given argument is more than 100, expect error embed to be sent', async () => { + const messageChannelSend = jest.fn(); + const messageChannelBulkDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend, + bulkDelete: messageChannelBulkDelete + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'clear', + args: ['101'], + message: message + }; + + const clear = new Clear(); + const result = await clear.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageChannelBulkDelete).not.toBeCalled(); + expect(result.embeds.length).toBe(1); + + // ErrorEmbed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.title).toBeNull(); + expect(errorEmbed.description).toBe('Please specify an amount between 1 and 100'); + }); +}); \ No newline at end of file diff --git a/tests/commands/eval.test.ts b/tests/commands/eval.test.ts new file mode 100644 index 0000000..a9c4929 --- /dev/null +++ b/tests/commands/eval.test.ts @@ -0,0 +1,136 @@ +import { Message } from "discord.js"; +import Evaluate from "../../src/commands/eval"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect values to be set', () => { + const evaluate = new Evaluate(); + + expect(evaluate._category).toBe('Owner'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect eval statement ran', () => { + process.env = { + BOT_OWNERID: 'OWNERID' + }; + + console.log = jest.fn(); + global.eval = jest.fn() + .mockReturnValue('General Kenobi'); + + const messageChannelSend = jest.fn(); + + const message = { + author: { + id: 'OWNERID' + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'eval', + args: ['echo', 'Hello', 'there'], + message: message + }; + + const evaluate = new Evaluate(); + + const result = evaluate.execute(context); + + expect(console.log).toBeCalledWith('Eval Statement: echo Hello there'); + expect(global.eval).toBeCalledWith('echo Hello there'); + expect(result.embeds.length).toBe(1); + + // PublicEmbed + const publicEmbed = result.embeds[0]; + + expect(publicEmbed.title).toBe(''); + expect(publicEmbed.description).toBe('General Kenobi'); + }); + + test('Given user does not have permission, expect nothing to occur', () => { + process.env = { + BOT_OWNERID: 'DIFFERENT' + }; + + console.log = jest.fn(); + global.eval = jest.fn() + .mockReturnValue('General Kenobi'); + + const messageChannelSend = jest.fn(); + + const message = { + author: { + id: 'OWNERID' + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'eval', + args: ['echo', 'Hello', 'there'], + message: message + }; + + const evaluate = new Evaluate(); + + const result = evaluate.execute(context); + + expect(console.log).not.toBeCalled(); + expect(global.eval).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given eval failed, expect error embed to be sent', () => { + process.env = { + BOT_OWNERID: 'OWNERID' + }; + + console.log = jest.fn(); + global.eval = jest.fn() + .mockImplementation(() => { + throw new Error('Error message'); + }); + + const messageChannelSend = jest.fn(); + + const message = { + author: { + id: 'OWNERID' + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'eval', + args: ['echo', 'Hello', 'there'], + message: message + }; + + const evaluate = new Evaluate(); + + const result = evaluate.execute(context); + + expect(console.log).toBeCalledWith('Eval Statement: echo Hello there'); + expect(global.eval).toBeCalledWith('echo Hello there'); + expect(result.embeds.length).toBe(1); + + // ErrorEmbed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.title).toBeNull(); + expect(errorEmbed.description).toBe('Error: Error message'); + }); +}); \ No newline at end of file diff --git a/tests/commands/help.test.ts b/tests/commands/help.test.ts new file mode 100644 index 0000000..8a0fa32 --- /dev/null +++ b/tests/commands/help.test.ts @@ -0,0 +1,267 @@ +import Help, { ICommandData } from "../../src/commands/help"; +import { Message } from "discord.js"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +const oldCwd = process.cwd(); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + const help = new Help(); + + expect(help._category).toBe('General'); + }); +}); + +describe('Execute', () => { + test('Given no arguments were given, expect SendAll to be executed', () => { + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'help', + args: [], + message: message + }; + + const help = new Help(); + + help.SendAll = jest.fn(); + help.SendSingle = jest.fn(); + + help.execute(context); + + expect(help.SendAll).toBeCalled(); + expect(help.SendSingle).not.toBeCalled(); + }); + + test('Given an argument was given, expect SendSingle to be executed', () => { + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'help', + args: ['about'], + message: message + }; + + const help = new Help(); + + help.SendAll = jest.fn(); + help.SendSingle = jest.fn(); + + help.execute(context); + + expect(help.SendAll).not.toBeCalled(); + expect(help.SendSingle).toBeCalled(); + }); +}); + +describe('SendAll', () => { + test('Expect embed with all commands to be sent', () => { + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'help', + args: [], + message: message + }; + + const help = new Help(); + + const commandData0: ICommandData = { + Exists: true, + Name: 'about', + Category: 'general', + Roles: [] + }; + + const commandData1: ICommandData = { + Exists: true, + Name: 'role', + Category: 'general', + Roles: [] + }; + + help.GetAllCommandData = jest.fn() + .mockReturnValue([commandData0, commandData1]); + + const result = help.SendAll(context); + + expect(help.GetAllCommandData).toBeCalled(); + expect(messageChannelSend).toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // PublicEmbed + const publicEmbed = result.embeds[0]; + + expect(publicEmbed.fields.length).toBe(1); + + // PublicEmbed -> GeneralCategory Field + const publicEmbedFieldGeneral = publicEmbed.fields[0]; + + expect(publicEmbedFieldGeneral.name).toBe('General'); + expect(publicEmbedFieldGeneral.value).toBe('about, role'); + }); +}); + +describe('SendSingle', () => { + test('Given command exists, expect embed to be sent with command fields', () => { + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'help', + args: ['about'], + message: message + }; + + const commandData: ICommandData = { + Exists: true, + Name: 'about', + Category: 'general', + Roles: ['role1', 'role2'] + }; + + const help = new Help(); + + help.GetCommandData = jest.fn() + .mockReturnValue(commandData); + + const result = help.SendSingle(context); + + expect(help.GetCommandData).toBeCalledWith('about'); + expect(messageChannelSend).toBeCalled(); + expect(result.embeds.length).toBe(1); + + // PublicEmbed + const publicEmbed = result.embeds[0]; + + expect(publicEmbed.title).toBe('About'); + expect(publicEmbed.description).toBe(''); + expect(publicEmbed.fields.length).toBe(2); + + // PublicEmbed -> Category Field + const fieldCategory = publicEmbed.fields[0]; + + expect(fieldCategory.name).toBe('Category'); + expect(fieldCategory.value).toBe('General'); + + // PublicEmbed -> RequiredRoles Field + const fieldRoles = publicEmbed.fields[1]; + + expect(fieldRoles.name).toBe('Required Roles'); + expect(fieldRoles.value).toBe('Role1, Role2'); + }); + + test('Given command does not exist, expect error embed to be sent', () => { + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'help', + args: ['about'], + message: message + }; + + const commandData: ICommandData = { + Exists: false + }; + + const help = new Help(); + + help.GetCommandData = jest.fn() + .mockReturnValue(commandData); + + const result = help.SendSingle(context); + + expect(help.GetCommandData).toBeCalledWith('about'); + expect(messageChannelSend).toBeCalled(); + expect(result.embeds.length).toBe(1); + + // ErrorEmbed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Command does not exist'); + }); +}); + +describe('GetAllCommandData', () => { + test('Expect array of command data to be returned', () => { + process.env = { + FOLDERS_COMMANDS: "commands" + }; + + process.cwd = jest.fn() + .mockReturnValue(`${oldCwd}/tests/_mocks`); + + const help = new Help(); + + const result = help.GetAllCommandData(); + + expect(result.length).toBe(1); + + // Mock Command + const mockCommand = result[0]; + + expect(mockCommand.Exists).toBeTruthy(); + expect(mockCommand.Name).toBe("mockCmd"); + expect(mockCommand.Category).toBe("General"); + + expect(mockCommand.Roles!.length).toBe(1); + expect(mockCommand.Roles![0]).toBe("Moderator"); + }); +}); + +describe('GetCommandData', () => { + test('Given command exists, expect data to be returned', () => { + process.env = { + FOLDERS_COMMANDS: "commands" + }; + + process.cwd = jest.fn() + .mockReturnValue(`${oldCwd}/tests/_mocks`); + + const help = new Help(); + + const result = help.GetCommandData('mockCmd'); + + expect(result.Exists).toBeTruthy(); + expect(result.Name).toBe("mockCmd"); + expect(result.Category).toBe("General"); + + expect(result.Roles!.length).toBe(1); + expect(result.Roles![0]).toBe("Moderator"); + }); + + test('Given command does not exist, expect exists false to be returned', () => { + process.env = { + FOLDERS_COMMANDS: "commands" + }; + + const oldCwd = process.cwd(); + + process.cwd = jest.fn() + .mockReturnValue(`${oldCwd}/tests/_mocks`); + + const help = new Help(); + + const result = help.GetCommandData('none'); + + expect(result.Exists).toBeFalsy(); + }); +}); diff --git a/tests/commands/kick.test.ts b/tests/commands/kick.test.ts new file mode 100644 index 0000000..c5ccd68 --- /dev/null +++ b/tests/commands/kick.test.ts @@ -0,0 +1,448 @@ +import { mock } from "jest-mock-extended"; + +import { GuildMember, Message, TextChannel, User } from "discord.js"; +import Kick from "../../src/commands/kick"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + ROLES_MODERATOR: "Moderator" + }; + + const kick = new Kick(); + + expect(kick._category).toBe('Moderation'); + expect(kick._roles.length).toBe(1); + expect(kick._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect user to be kicked', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const member = { + kickable: true, + kick: jest.fn() + } as unknown as GuildMember; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: "kick", + args: ["USER", "Test", "Reason"], + message: message + } + + const kick = new Kick(); + + const result = await kick.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).toBeCalledTimes(1); + expect(member.kick).toBeCalledWith('Test Reason'); + + expect(result.embeds.length).toBe(2); + + // Log Embed + const logEmbed = result.embeds[0]; + + expect(logEmbed.title).toBe('Member Kicked'); + expect(logEmbed.fields.length).toBe(3); + + // Log Embed -> User Field + const logEmbedFieldUser = logEmbed.fields[0]; + + expect(logEmbedFieldUser.name).toBe('User'); + expect(logEmbedFieldUser.value).toBe('[object Object] `USERTAG`'); + expect(logEmbedFieldUser.inline).toBeTruthy(); + + // Log Embed -> Moderator Field + const logEmbedFieldModerator = logEmbed.fields[1]; + + expect(logEmbedFieldModerator.name).toBe('Moderator'); + expect(logEmbedFieldModerator.value).toBe('[object Object] `AUTHORTAG`'); + + // Log Embed -> Reason Field + const logEmbedFieldReason = logEmbed.fields[2]; + + expect(logEmbedFieldReason.name).toBe('Reason'); + expect(logEmbedFieldReason.value).toBe('Test Reason'); + }); + + test('Given target user is not found, expect user does not exist error', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const member = { + kickable: true, + kick: jest.fn() + } as unknown as GuildMember; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(null); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: "kick", + args: ["USER", "Test", "Reason"], + message: message + } + + const kick = new Kick(); + + const result = await kick.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(member.kick).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(null); + expect(embed.description).toBe('User does not exist'); + }); + + test('Given target member is not found, expect user is not in this server error', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const member = { + kickable: true, + kick: jest.fn() + } as unknown as GuildMember; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(null); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: "kick", + args: ["USER", "Test", "Reason"], + message: message + } + + const kick = new Kick(); + + const result = await kick.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(member.kick).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(null); + expect(embed.description).toBe('User is not in this server'); + }); + + test('Given guild is not available, expect to stop', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const member = { + kickable: true, + kick: jest.fn() + } as unknown as GuildMember; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: false + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: "kick", + args: ["USER", "Test", "Reason"], + message: message + } + + const kick = new Kick(); + + const result = await kick.execute(context); + + expect(messageChannelSend).not.toBeCalled(); + expect(logChannel.send).not.toBeCalled(); + expect(member.kick).not.toBeCalled(); + + expect(result.embeds.length).toBe(0); + }); + + test('Given client can not kick member, expect error', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const member = { + kickable: false, + kick: jest.fn() + } as unknown as GuildMember; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: "kick", + args: ["USER", "Test", "Reason"], + message: message + } + + const kick = new Kick(); + + const result = await kick.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(member.kick).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(null); + expect(embed.description).toBe('Unable to do this action, am I missing permissions?'); + }); +}); \ No newline at end of file diff --git a/tests/commands/mute.test.ts b/tests/commands/mute.test.ts new file mode 100644 index 0000000..a0137f5 --- /dev/null +++ b/tests/commands/mute.test.ts @@ -0,0 +1,675 @@ +import { mock } from "jest-mock-extended"; + +import { GuildMember, Message, Role, TextChannel, User } from "discord.js"; +import Mute from "../../src/commands/mute"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + ROLES_MODERATOR: 'Moderator' + }; + + const mute = new Mute(); + + expect(mute._category).toBe("Moderation"); + expect(mute._roles.length).toBe(1); + expect(mute._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect user to be given muted role', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).toBeCalledTimes(1); + expect(member.roles.add).toBeCalledWith(role, 'Test Reason'); + + expect(result.embeds.length).toBe(2); + + // Log Embed + const logEmbed = result.embeds[0]; + + expect(logEmbed.title).toBe('Member Muted'); + expect(logEmbed.fields.length).toBe(3); + + // Log Embed -> User Field + const logEmbedUserField = logEmbed.fields[0]; + + expect(logEmbedUserField.name).toBe('User'); + expect(logEmbedUserField.value).toBe('[object Object] `USERTAG`'); + expect(logEmbedUserField.inline).toBeTruthy(); + + // Log Embed -> Moderator Field + const logEmbedModeratorField = logEmbed.fields[1]; + + expect(logEmbedModeratorField.name).toBe('Moderator'); + expect(logEmbedModeratorField.value).toBe('[object Object] `AUTHORTAG`'); + + // Public Embed + const publicEmbed = result.embeds[1]; + + expect(publicEmbed.title).toBe(''); + expect(publicEmbed.description).toBe('[object Object] has been muted'); + }); + + test('Given user did not mention a user, expect user not to exist', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(null); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).not.toBeCalled(); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('User does not exist'); + }); + + test('Given member can not be found from user, expect user to not be in server', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(null); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('User is not in this server'); + }); + + test('Given guild is unavailable, expect execution to stop', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: false, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).not.toBeCalled(); + + expect(result.embeds.length).toBe(0); + }); + + test('Given client can not manage user, expect insufficient permissions', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: false, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Unable to do this action, am I missing permissions?'); + }); + + test('Given muted role can not be found, expect role not found', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Unable to find role'); + }); +}); \ No newline at end of file diff --git a/tests/commands/poll.test.ts b/tests/commands/poll.test.ts new file mode 100644 index 0000000..4f3727d --- /dev/null +++ b/tests/commands/poll.test.ts @@ -0,0 +1,262 @@ +import { Message, MessageEmbed } from "discord.js"; +import Poll from "../../src/commands/poll"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +describe('Constructor', () => { + test('Expect properties to be set', () => { + const poll = new Poll(); + + expect(poll._category).toBe('General'); + }); +}); + +describe('Execute', () => { + test('Given input is valid, expect poll to be generated', async () => { + const returnMessageReact = jest.fn(); + + const returnMessage = { + react: returnMessageReact + } as unknown as Message; + + const messageChannelSend = jest.fn() + .mockReturnValue(returnMessage); + const messageDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + delete: messageDelete, + deletable: true + } as unknown as Message; + + const context: ICommandContext = { + name: 'poll', + args: ['Test', 'title;', 'one;', 'two'], + message: message + }; + + const poll = new Poll(); + + const result = await poll.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageDelete).toBeCalledTimes(1); + expect(returnMessageReact).toBeCalledTimes(2); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Test title'); + expect(embed.description).toBe(':one: one\n:two: two'); + }); + + test('Given message is not deletable by client, expect it not to attempt deletion', async () => { + const returnMessageReact = jest.fn(); + + const returnMessage = { + react: returnMessageReact + } as unknown as Message; + + const messageChannelSend = jest.fn() + .mockReturnValue(returnMessage); + const messageDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + delete: messageDelete, + deletable: false + } as unknown as Message; + + const context: ICommandContext = { + name: 'poll', + args: ['Test', 'title;', 'one;', 'two'], + message: message + }; + + const poll = new Poll(); + + const result = await poll.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageDelete).not.toBeCalled(); + expect(returnMessageReact).toBeCalledTimes(2); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Test title'); + expect(embed.description).toBe(':one: one\n:two: two'); + }); + + test('Given no arguments, expect error embed', async () => { + const returnMessageReact = jest.fn(); + + const returnMessage = { + react: returnMessageReact + } as unknown as Message; + + const messageChannelSend = jest.fn() + .mockReturnValue(returnMessage); + const messageDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + delete: messageDelete, + deletable: true + } as unknown as Message; + + const context: ICommandContext = { + name: 'poll', + args: [], + message: message + }; + + const poll = new Poll(); + + const result = await poll.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageDelete).not.toBeCalled(); + expect(returnMessageReact).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options'); + }); + + test('Given only 1 option, expect error embed', async () => { + const returnMessageReact = jest.fn(); + + const returnMessage = { + react: returnMessageReact + } as unknown as Message; + + const messageChannelSend = jest.fn() + .mockReturnValue(returnMessage); + const messageDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + delete: messageDelete, + deletable: true + } as unknown as Message; + + const context: ICommandContext = { + name: 'poll', + args: ['Test', 'title;', 'one'], + message: message + }; + + const poll = new Poll(); + + const result = await poll.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageDelete).not.toBeCalled(); + expect(returnMessageReact).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options'); + }); + + test('Given 9 options, expect poll to be generated', async () => { + const returnMessageReact = jest.fn(); + + const returnMessage = { + react: returnMessageReact + } as unknown as Message; + + const messageChannelSend = jest.fn() + .mockReturnValue(returnMessage); + const messageDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + delete: messageDelete, + deletable: true + } as unknown as Message; + + const context: ICommandContext = { + name: 'poll', + args: ['Test', 'title;', 'one;', 'two;', 'three;', 'four;', 'five;', 'six;', 'seven;', 'eight;', 'nine'], + message: message + }; + + const poll = new Poll(); + + const result = await poll.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageDelete).toBeCalledTimes(1); + expect(returnMessageReact).toBeCalledTimes(9); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Test title'); + expect(embed.description).toBe(':one: one\n:two: two\n:three: three\n:four: four\n:five: five\n:six: six\n:seven: seven\n:eight: eight\n:nine: nine'); + }); + + test('Given 10 options, expect error embed', async () => { + const returnMessageReact = jest.fn(); + + const returnMessage = { + react: returnMessageReact + } as unknown as Message; + + const messageChannelSend = jest.fn() + .mockReturnValue(returnMessage); + const messageDelete = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + delete: messageDelete, + deletable: true + } as unknown as Message; + + const context: ICommandContext = { + name: 'poll', + args: ['Test', 'title;', 'one;', 'two;', 'three;', 'four;', 'five;', 'six;', 'seven;', 'eight;', 'nine;', 'ten'], + message: message + }; + + const poll = new Poll(); + + const result = await poll.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(messageDelete).not.toBeCalled(); + expect(returnMessageReact).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options'); + }); +}); \ No newline at end of file diff --git a/tests/commands/role.test.ts b/tests/commands/role.test.ts new file mode 100644 index 0000000..f72d5da --- /dev/null +++ b/tests/commands/role.test.ts @@ -0,0 +1,411 @@ +import { GuildMemberRoleManager, Message, Role as DiscordRole } from "discord.js"; +import { mock } from "jest-mock-extended"; +import Role from "../../src/commands/role"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties are set', () => { + const role = new Role(); + + expect(role._category).toBe("General"); + }); +}); + +describe('Execute', () => { + test('Given no arguments were given, expect SendRolesList to be executed', async () => { + process.env = { + COMMANDS_ROLE_ROLES: 'One,Two' + }; + + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: [], + message: message + }; + + const role = new Role(); + + role.SendRolesList = jest.fn(); + role.ToggleRole = jest.fn(); + + await role.execute(context); + + expect(role.SendRolesList).toBeCalledWith(context, ['One', 'Two']); + expect(role.ToggleRole).not.toBeCalled(); + }); + + test('Given an argument was given, expect ToggleRole to be executed', async () => { + process.env = { + COMMANDS_ROLE_ROLES: 'One,Two' + }; + + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const role = new Role(); + + role.SendRolesList = jest.fn(); + role.ToggleRole = jest.fn(); + + await role.execute(context); + + expect(role.SendRolesList).not.toBeCalled(); + expect(role.ToggleRole).toBeCalledWith(context, ['One', 'Two']); + }); +}); + +describe('SendRolesList', () => { + test('Expect embed with roles to be sent to the current channel', () => { + process.env = { + BOT_PREFIX: '!' + }; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: [], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + const result = role.SendRolesList(context, roles); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Roles'); + expect(embed.description).toBe('Do !role <role> to get the role!\nOne\nTwo'); + }); +}); + +describe('ToggleRole', () => { + test('Given role name is a valid role AND user does not have the role, expect role to be added', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).not.toBeCalled(); + expect(role.AddRole).toBeCalledWith(context, discordRole); + expect(role.RemoveRole).not.toBeCalled(); + + expect(result.embeds.length).toBe(0); + }); + + test('Given role name is a valid role AND user has the role, expect role to be removed', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).not.toBeCalled(); + expect(role.AddRole).not.toBeCalled(); + expect(role.RemoveRole).toBeCalledWith(context, discordRole); + + expect(result.embeds.length).toBe(0); + }); + + test('Given role requested is not in the roles array, expect role not assignable error', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['Three'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).not.toBeCalled(); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + expect(role.AddRole).not.toBeCalled(); + expect(role.RemoveRole).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe("This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters"); + }); + + test('Given the role is not in the guild, expect error', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).not.toBeCalled(); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).toBeCalledTimes(1); + expect(role.AddRole).not.toBeCalled(); + expect(role.RemoveRole).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe("The current server doesn't have this role. Please contact the server's moderators"); + }); +}); + +describe('AddRole', () => { + test('Expect role to be added to user', async () => { + const messageChannelSend = jest.fn(); + + const guildMemberRoleManager = mock<GuildMemberRoleManager>(); + + const message = { + member: { + roles: guildMemberRoleManager + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const discordRole = { + name: 'One' + } as unknown as DiscordRole; + + const role = new Role(); + + const result = await role.AddRole(context, discordRole); + + expect(guildMemberRoleManager.add).toBeCalledWith(discordRole); + expect(messageChannelSend).toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(''); + expect(embed.description).toBe('Gave role: One'); + }); +}); + +describe('RemoveRole', () => { + test('Expect role to be removed from user', async () => { + const messageChannelSend = jest.fn(); + + const guildMemberRoleManager = mock<GuildMemberRoleManager>(); + + const message = { + member: { + roles: guildMemberRoleManager + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const discordRole = { + name: 'One' + } as unknown as DiscordRole; + + const role = new Role(); + + const result = await role.RemoveRole(context, discordRole); + + expect(guildMemberRoleManager.remove).toBeCalledWith(discordRole); + expect(messageChannelSend).toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(''); + expect(embed.description).toBe('Removed role: One'); + }); +}); \ No newline at end of file diff --git a/tests/commands/rules.test.ts b/tests/commands/rules.test.ts new file mode 100644 index 0000000..4c390a5 --- /dev/null +++ b/tests/commands/rules.test.ts @@ -0,0 +1,106 @@ +import { Message } from "discord.js"; +import Rules from "../../src/commands/rules"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +const oldCwd = process.cwd(); + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + ROLES_MODERATOR: "Moderator" + }; + + const rules = new Rules(); + + expect(rules._category).toBe("Admin"); + expect(rules._roles.length).toBe(1); + expect(rules._roles[0]).toBe("Moderator"); + }); +}); + +describe('Execute', () => { + test('Given rules exist, expect rules to be sent to current channel', () => { + process.env = { + COMMANDS_RULES_FILE: 'rules/rules.json' + }; + + process.cwd = jest.fn() + .mockReturnValue(`${oldCwd}/tests/_mocks`); + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'rules', + args: [], + message: message + }; + + const rules = new Rules(); + + const result = rules.execute(context); + + expect(messageChannelSend).toBeCalledTimes(2); + expect(result.embeds.length).toBe(2); + + // Header Embed + const embedHeader = result.embeds[0]; + + expect(embedHeader.title).toBe(""); + expect(embedHeader.description).toBe(""); + expect(embedHeader.image?.url).toBe("IMAGEURL"); + expect(embedHeader.footer?.text).toBe(""); + + // Main Embed + const embedMain = result.embeds[1]; + + expect(embedMain.title).toBe("TITLE 1"); + expect(embedMain.description).toBe("DESCRIPTION 1A\nDESCRIPTION 1B"); + expect(embedMain.image?.url).toBe(""); + expect(embedMain.footer?.text).toBe("FOOTER 1"); + }); + + test('Given rules file does not exist, expect does not exist error', () => { + process.env = { + COMMANDS_RULES_FILE: 'rules/none.json' + }; + + process.cwd = jest.fn() + .mockReturnValue(`${oldCwd}/tests/_mocks`); + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'rules', + args: [], + message: message + }; + + const rules = new Rules(); + + const result = rules.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe("Rules file doesn't exist"); + }); +}); \ No newline at end of file diff --git a/tests/commands/unmute.test.ts b/tests/commands/unmute.test.ts new file mode 100644 index 0000000..1449b39 --- /dev/null +++ b/tests/commands/unmute.test.ts @@ -0,0 +1,673 @@ +import { GuildMember, Message, Role, TextChannel, User } from "discord.js"; +import Unmute from "../../src/commands/unmute"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + ROLES_MODERATOR: 'Moderator' + }; + + const mute = new Unmute(); + + expect(mute._category).toBe("Moderation"); + expect(mute._roles.length).toBe(1); + expect(mute._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect user to be given muted role', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).toBeCalledTimes(1); + expect(member.roles.remove).toBeCalledWith(role, 'Test Reason'); + + expect(result.embeds.length).toBe(2); + + // Log Embed + const logEmbed = result.embeds[0]; + + expect(logEmbed.title).toBe('Member Unmuted'); + expect(logEmbed.fields.length).toBe(3); + + // Log Embed -> User Field + const logEmbedUserField = logEmbed.fields[0]; + + expect(logEmbedUserField.name).toBe('User'); + expect(logEmbedUserField.value).toBe('[object Object] `USERTAG`'); + expect(logEmbedUserField.inline).toBeTruthy(); + + // Log Embed -> Moderator Field + const logEmbedModeratorField = logEmbed.fields[1]; + + expect(logEmbedModeratorField.name).toBe('Moderator'); + expect(logEmbedModeratorField.value).toBe('[object Object] `AUTHORTAG`'); + + // Public Embed + const publicEmbed = result.embeds[1]; + + expect(publicEmbed.title).toBe(''); + expect(publicEmbed.description).toBe('[object Object] has been unmuted'); + }); + + test('Given user did not mention a user, expect user not to exist', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(null); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).not.toBeCalled(); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('User does not exist'); + }); + + test('Given member can not be found from user, expect user to not be in server', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(null); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('User is not in this server'); + }); + + test('Given guild is unavailable, expect execution to stop', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: false, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).not.toBeCalled(); + + expect(result.embeds.length).toBe(0); + }); + + test('Given client can not manage user, expect insufficient permissions', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: false, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Unable to do this action, am I missing permissions?'); + }); + + test('Given muted role can not be found, expect role not found', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER', 'Test', 'Reason'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildChannelsCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe('Unable to find role'); + }); +}); \ No newline at end of file diff --git a/tests/commands/warn.test.ts b/tests/commands/warn.test.ts new file mode 100644 index 0000000..f77ba0a --- /dev/null +++ b/tests/commands/warn.test.ts @@ -0,0 +1,485 @@ +import { GuildMember, Message, TextChannel, User } from "discord.js"; +import Warn from "../../src/commands/warn"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect values to be set', () => { + process.env.ROLES_MODERATOR = 'Moderator'; + + const warn = new Warn(); + + expect(warn._category).toBe('Moderation'); + expect(warn._roles.length).toBe(1); + expect(warn._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect user to be warnned', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + warnnable: true, + warn: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'warn', + args: ['warn', 'Test', 'Reason'], + message: message + }; + + const warn = new Warn(); + + const result = await warn.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).toBeCalledTimes(1); + }); + + test('Given user has permissions, expect embeds to be correct', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + warnnable: true, + warn: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'warn', + args: ['warn', 'Test', 'Reason'], + message: message + }; + + const warn = new Warn(); + + const result = await warn.execute(context); + + expect(result.embeds.length).toBe(2); + + const logEmbed = result.embeds[0]; + const publicEmbed = result.embeds[1]; + + expect(logEmbed.title).toBe('Member Warned'); + expect(publicEmbed.title).toBe(""); + expect(publicEmbed.description).toBe('[object Object] has been warned'); + expect(logEmbed.fields.length).toBe(3); + expect(publicEmbed.fields.length).toBe(1); + expect(publicEmbed.fields[0].name).toBe('Reason'); + expect(publicEmbed.fields[0].value).toBe('Test Reason'); + }); + + test('Given user has permission, expect logEmbed fields to be correct', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn().mockReturnValue('URL'), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + warnnable: true, + warn: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'warn', + args: ['warn', 'Test', 'Reason'], + message: message + }; + + const warn = new Warn(); + + const result = await warn.execute(context); + + const logEmbed = result.embeds[0]; + + const fieldUser = logEmbed.fields[0]; + const fieldModerator = logEmbed.fields[1]; + const fieldReason = logEmbed.fields[2]; + + expect(fieldUser.name).toBe("User"); + expect(fieldUser.value).toBe("[object Object] `USERTAG`"); + expect(logEmbed.thumbnail?.url).toBe("URL"); + + expect(fieldModerator.name).toBe('Moderator'); + expect(fieldModerator.value).toBe('[object Object] `AUTHORTAG`'); + + expect(fieldReason.name).toBe('Reason'); + expect(fieldReason.value).toBe('Test Reason'); + }); + + test('Given user is not mentioned, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedMember = { + warnnable: true, + warn: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(null); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'warn', + args: ['warn', 'Test', 'Reason'], + message: message + }; + + const warn = new Warn(); + + const result = await warn.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('User does not exist'); + }); + + test('Given member is not in server, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + warnnable: true, + warn: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(null); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'warn', + args: ['warn', 'Test', 'Reason'], + message: message + }; + + const warn = new Warn(); + + const result = await warn.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('User is not in this server'); + }); + + test('Given guild is unavailable, expect return and do nothing', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + warnnable: true, + warn: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: false + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'warn', + args: ['warn', 'Test', 'Reason'], + message: message + }; + + const warn = new Warn(); + + const result = await warn.execute(context); + + expect(messageChannelSend).not.toBeCalled(); + expect(logChannel.send).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); +}); \ No newline at end of file diff --git a/tests/events/MemberEvents.test.ts b/tests/events/MemberEvents.test.ts new file mode 100644 index 0000000..ad16485 --- /dev/null +++ b/tests/events/MemberEvents.test.ts @@ -0,0 +1,164 @@ +import { GuildMember, TextChannel, User } from "discord.js"; +import MemberEvents from "../../src/events/MemberEvents"; +import GuildMemberUpdate from "../../src/events/MemberEvents/GuildMemberUpdate"; + +describe('GuildMemberAdd', () => { + test('When event is fired, expect embed to be sent to logs channel', () => { + const currentDate = new Date(); + + const textChannel = { + send: jest.fn() + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const userDisplayAvatarURL = jest.fn(); + + const guildUser = { + tag: 'USERTAG', + createdAt: currentDate, + id: 'USERID', + displayAvatarURL: userDisplayAvatarURL + } as unknown as User; + + const guildMember = { + user: guildUser, + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + } + } as unknown as GuildMember; + + const memberEvents = new MemberEvents(); + + const result = memberEvents.guildMemberAdd(guildMember); + + expect(textChannel.send).toBeCalledTimes(1); + expect(userDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe("Member Joined"); + expect(embed.footer?.text).toBe("Id: USERID"); + expect(embed.fields.length).toBe(2); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe("User"); + expect(embedFieldUser.value).toBe("[object Object] `USERTAG`"); + expect(embedFieldUser.inline).toBeTruthy(); + + // Embed -> Created Field + const embedFieldCreated = embed.fields[1]; + + expect(embedFieldCreated.name).toBe("Created"); + expect(embedFieldCreated.value).toBe(currentDate.toString()); + }); +}); + +describe('GuildMemberRemove', () => { + test('When event is fired, expect embed to be sent to logs channel', () => { + const currentDate = new Date(); + + const textChannel = { + send: jest.fn() + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const userDisplayAvatarURL = jest.fn(); + + const guildUser = { + tag: 'USERTAG', + createdAt: currentDate, + id: 'USERID', + displayAvatarURL: userDisplayAvatarURL + } as unknown as User; + + const guildMember = { + user: guildUser, + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + joinedAt: currentDate + } as unknown as GuildMember; + + const memberEvents = new MemberEvents(); + + const result = memberEvents.guildMemberRemove(guildMember); + + expect(textChannel.send).toBeCalledTimes(1); + expect(userDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe("Member Left"); + expect(embed.footer?.text).toBe("Id: USERID"); + expect(embed.fields.length).toBe(2); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe("User"); + expect(embedFieldUser.value).toBe("[object Object] `USERTAG`"); + expect(embedFieldUser.inline).toBeTruthy(); + + // Embed -> Joined Field + const embedFieldJoined = embed.fields[1]; + + expect(embedFieldJoined.name).toBe("Joined"); + expect(embedFieldJoined.value).toBe(currentDate.toString()); + }); +}); + +describe('GuildMemberUpdate', () => { + test('Given nicknames are the same, expect NicknameChanged NOT to be called', () => { + const member = { + nickname: 'member' + } as unknown as GuildMember; + + const nicknameChanged = jest.fn(); + + GuildMemberUpdate.prototype.NicknameChanged = nicknameChanged; + + const memberEvents = new MemberEvents(); + + const result = memberEvents.guildMemberUpdate(member, member); + + expect(result.embeds.length).toBe(0); + expect(nicknameChanged).not.toBeCalled(); + }); + + test('Given nicknames are the different, expect NicknameChanged to be called', () => { + const oldMember = { + nickname: 'oldMember' + } as unknown as GuildMember; + + const newMember = { + nickname: 'newMember' + } as unknown as GuildMember; + + const nicknameChanged = jest.fn(); + + GuildMemberUpdate.prototype.NicknameChanged = nicknameChanged; + + const memberEvents = new MemberEvents(); + + const result = memberEvents.guildMemberUpdate(oldMember, newMember); + + expect(result.embeds.length).toBe(0); + expect(nicknameChanged).toBeCalledTimes(1); + }); +}); \ No newline at end of file diff --git a/tests/events/MemberEvents/GuildMemberUpdate.test.ts b/tests/events/MemberEvents/GuildMemberUpdate.test.ts new file mode 100644 index 0000000..1697cf9 --- /dev/null +++ b/tests/events/MemberEvents/GuildMemberUpdate.test.ts @@ -0,0 +1,235 @@ +import { GuildMember, TextChannel } from "discord.js"; +import GuildMemberUpdate from "../../../src/events/MemberEvents/GuildMemberUpdate"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties are set', () => { + const oldMember = { + nickname: 'Old Nickname' + } as unknown as GuildMember; + + const newMember = { + nickname: 'New Nickname' + } as unknown as GuildMember; + + const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); + + expect(guildMemberUpdate.oldMember).toBe(oldMember); + expect(guildMemberUpdate.newMember).toBe(newMember); + }); +}); + +describe('NicknameChanged', () => { + test('Given nickname has changed from one to another, expect embed to be sent with both', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const memberUserDisplayAvatarURL = jest.fn(); + + const oldMember = { + nickname: 'Old Nickname' + } as unknown as GuildMember; + + const newMember = { + nickname: 'New Nickname', + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + user: { + tag: 'USERTAG', + id: 'USERID', + displayAvatarURL: memberUserDisplayAvatarURL + } + } as unknown as GuildMember; + + const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); + + const result = guildMemberUpdate.NicknameChanged(); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(memberUserDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Nickname Changed'); + expect(embed.footer?.text).toBe('Id: USERID'); + expect(embed.fields.length).toBe(3); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + + // Embed -> Before Field + const embedFieldBefore = embed.fields[1]; + + expect(embedFieldBefore.name).toBe('Before'); + expect(embedFieldBefore.value).toBe('Old Nickname'); + + // Embed -> After Field + const embedFieldAfter = embed.fields[2]; + + expect(embedFieldAfter.name).toBe('After'); + expect(embedFieldAfter.value).toBe('New Nickname'); + }); + + test('Given old nickname was null, expect embed to say old nickname was none', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const memberUserDisplayAvatarURL = jest.fn(); + + const oldMember = {} as unknown as GuildMember; + + const newMember = { + nickname: 'New Nickname', + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + user: { + tag: 'USERTAG', + id: 'USERID', + displayAvatarURL: memberUserDisplayAvatarURL + } + } as unknown as GuildMember; + + const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); + + const result = guildMemberUpdate.NicknameChanged(); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(memberUserDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Nickname Changed'); + expect(embed.footer?.text).toBe('Id: USERID'); + expect(embed.fields.length).toBe(3); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + + // Embed -> Before Field + const embedFieldBefore = embed.fields[1]; + + expect(embedFieldBefore.name).toBe('Before'); + expect(embedFieldBefore.value).toBe('*none*'); + + // Embed -> After Field + const embedFieldAfter = embed.fields[2]; + + expect(embedFieldAfter.name).toBe('After'); + expect(embedFieldAfter.value).toBe('New Nickname'); + }); + + test('Given new nickname was null, expect embed to say new nickname was none', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const memberUserDisplayAvatarURL = jest.fn(); + + const oldMember = { + nickname: 'Old Nickname' + } as unknown as GuildMember; + + const newMember = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + user: { + tag: 'USERTAG', + id: 'USERID', + displayAvatarURL: memberUserDisplayAvatarURL + } + } as unknown as GuildMember; + + const guildMemberUpdate = new GuildMemberUpdate(oldMember, newMember); + + const result = guildMemberUpdate.NicknameChanged(); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(memberUserDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Nickname Changed'); + expect(embed.footer?.text).toBe('Id: USERID'); + expect(embed.fields.length).toBe(3); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + + // Embed -> Before Field + const embedFieldBefore = embed.fields[1]; + + expect(embedFieldBefore.name).toBe('Before'); + expect(embedFieldBefore.value).toBe('Old Nickname'); + + // Embed -> After Field + const embedFieldAfter = embed.fields[2]; + + expect(embedFieldAfter.name).toBe('After'); + expect(embedFieldAfter.value).toBe('*none*'); + }); +}); \ No newline at end of file diff --git a/tests/events/MessageEvents.test.ts b/tests/events/MessageEvents.test.ts new file mode 100644 index 0000000..abecc57 --- /dev/null +++ b/tests/events/MessageEvents.test.ts @@ -0,0 +1,648 @@ +import { Collection, Message, MessageAttachment, TextChannel } from "discord.js"; +import MessageEvents from "../../src/events/MessageEvents"; + +beforeEach(() => { + process.env = {}; +}); + +describe('MessageDelete', () => { + test('Given message was in a guild AND user was NOT a bot, expect message deleted embed to be sent', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const messageAttachments = new Collection<string, MessageAttachment>([ + [ + "0", + { + url: 'image0.png' + } as unknown as MessageAttachment + ], + [ + "1", + { + url: 'image1.png' + } as unknown as MessageAttachment + ] + ]); + + const message = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + channel: {}, + content: 'Message Content', + attachments: messageAttachments + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageDelete(message); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageAuthorDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Message Deleted'); + expect(embed.fields.length).toBe(4); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + + // Embed -> Channel Field + const embedFieldChannel = embed.fields[1]; + + expect(embedFieldChannel.name).toBe('Channel'); + expect(embedFieldChannel.value).toBe('[object Object]'); + + // Embed -> Content Field + const embedFieldContent = embed.fields[2]; + + expect(embedFieldContent.name).toBe('Content'); + expect(embedFieldContent.value).toBe('```Message Content```'); + + // Embed -> Attachments Field + const embedFieldAttachments = embed.fields[3]; + + expect(embedFieldAttachments.name).toBe('Attachments'); + expect(embedFieldAttachments.value).toBe('```image0.png\nimage1.png```'); + }); + + test('Given message was not sent in a guild, expect execution stopped', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const messageAttachments = new Collection<string, MessageAttachment>([ + [ + "0", + { + url: 'image0.png' + } as unknown as MessageAttachment + ], + [ + "1", + { + url: 'image1.png' + } as unknown as MessageAttachment + ] + ]); + + const message = { + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + channel: {}, + content: 'Message Content', + attachments: messageAttachments + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageDelete(message); + + expect(channelSend).not.toBeCalled(); + expect(memberGuildChannelsCacheFind).not.toBeCalled(); + expect(messageAuthorDisplayAvatarURL).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given author is a bot, expect execution stopped', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const messageAttachments = new Collection<string, MessageAttachment>([ + [ + "0", + { + url: 'image0.png' + } as unknown as MessageAttachment + ], + [ + "1", + { + url: 'image1.png' + } as unknown as MessageAttachment + ] + ]); + + const message = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: true, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + channel: {}, + content: 'Message Content', + attachments: messageAttachments + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageDelete(message); + + expect(channelSend).not.toBeCalled(); + expect(memberGuildChannelsCacheFind).not.toBeCalled(); + expect(messageAuthorDisplayAvatarURL).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given message does not contain any attachments, expect attachments field to be omitted', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const messageAttachments = new Collection<string, MessageAttachment>([]); + + const message = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + channel: {}, + content: 'Message Content', + attachments: messageAttachments + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageDelete(message); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageAuthorDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Message Deleted'); + expect(embed.fields.length).toBe(3); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + + // Embed -> Channel Field + const embedFieldChannel = embed.fields[1]; + + expect(embedFieldChannel.name).toBe('Channel'); + expect(embedFieldChannel.value).toBe('[object Object]'); + + // Embed -> Content Field + const embedFieldContent = embed.fields[2]; + + expect(embedFieldContent.name).toBe('Content'); + expect(embedFieldContent.value).toBe('```Message Content```'); + }); +}); + +describe('MessageUpdate', () => { + test('Given message is in a guild AND user is not a bot AND the content has actually changed, e xpect log embed to be sent', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const oldMessage = { + content: 'Old Message' + } as unknown as Message; + + const newMessage = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + content: 'New Message', + channel: {}, + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageUpdate(oldMessage, newMessage); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageAuthorDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Message Edited'); + expect(embed.fields.length).toBe(4); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + expect(embedFieldUser.inline).toBeTruthy(); + + // Embed -> Channel Field + const embedFieldChannel = embed.fields[1]; + + expect(embedFieldChannel.name).toBe('Channel'); + expect(embedFieldChannel.value).toBe('[object Object]'); + expect(embedFieldChannel.inline).toBeTruthy(); + + // Embed -> Before Field + const embedFieldBefore = embed.fields[2]; + + expect(embedFieldBefore.name).toBe('Before'); + expect(embedFieldBefore.value).toBe('```Old Message```'); + + // Embed -> After Field + const embedFieldAfter = embed.fields[3]; + + expect(embedFieldAfter.name).toBe('After'); + expect(embedFieldAfter.value).toBe('```New Message```'); + }); + + test('Given message was not in a guild, expect execution stopped', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const oldMessage = { + content: 'Old Message' + } as unknown as Message; + + const newMessage = { + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + content: 'New Message', + channel: {}, + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageUpdate(oldMessage, newMessage); + + expect(channelSend).not.toBeCalled(); + expect(memberGuildChannelsCacheFind).not.toBeCalled(); + expect(messageAuthorDisplayAvatarURL).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given author is a bot, expect execution stopped', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const oldMessage = { + content: 'Old Message' + } as unknown as Message; + + const newMessage = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: true, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + content: 'New Message', + channel: {}, + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageUpdate(oldMessage, newMessage); + + expect(channelSend).not.toBeCalled(); + expect(memberGuildChannelsCacheFind).not.toBeCalled(); + expect(messageAuthorDisplayAvatarURL).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given the message contents are the same, expect execution stopped', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const oldMessage = { + content: 'Message' + } as unknown as Message; + + const newMessage = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + content: 'Message', + channel: {}, + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageUpdate(oldMessage, newMessage); + + expect(channelSend).not.toBeCalled(); + expect(memberGuildChannelsCacheFind).not.toBeCalled(); + expect(messageAuthorDisplayAvatarURL).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given Old Message did not have a content, expect field to account for this', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const oldMessage = {} as unknown as Message; + + const newMessage = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + content: 'New Message', + channel: {}, + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageUpdate(oldMessage, newMessage); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageAuthorDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Message Edited'); + expect(embed.fields.length).toBe(4); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + expect(embedFieldUser.inline).toBeTruthy(); + + // Embed -> Channel Field + const embedFieldChannel = embed.fields[1]; + + expect(embedFieldChannel.name).toBe('Channel'); + expect(embedFieldChannel.value).toBe('[object Object]'); + expect(embedFieldChannel.inline).toBeTruthy(); + + // Embed -> Before Field + const embedFieldBefore = embed.fields[2]; + + expect(embedFieldBefore.name).toBe('Before'); + expect(embedFieldBefore.value).toBe('```*none*```'); + + // Embed -> After Field + const embedFieldAfter = embed.fields[3]; + + expect(embedFieldAfter.name).toBe('After'); + expect(embedFieldAfter.value).toBe('```New Message```'); + }); + + test('Given New Message does not have a content, expect field to account for this', () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const textChannel = { + name: 'mod-logs', + send: channelSend + } as unknown as TextChannel; + + const memberGuildChannelsCacheFind = jest.fn() + .mockReturnValue(textChannel); + const messageAuthorDisplayAvatarURL = jest.fn(); + + const oldMessage = { + content: 'Old Message' + } as unknown as Message; + + const newMessage = { + guild: { + channels: { + cache: { + find: memberGuildChannelsCacheFind + } + } + }, + author: { + bot: false, + displayAvatarURL: messageAuthorDisplayAvatarURL, + tag: 'USERTAG' + }, + channel: {}, + } as unknown as Message; + + const messageEvents = new MessageEvents(); + + const result = messageEvents.messageUpdate(oldMessage, newMessage); + + expect(channelSend).toBeCalledTimes(1); + expect(memberGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageAuthorDisplayAvatarURL).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Message Edited'); + expect(embed.fields.length).toBe(4); + + // Embed -> User Field + const embedFieldUser = embed.fields[0]; + + expect(embedFieldUser.name).toBe('User'); + expect(embedFieldUser.value).toBe('[object Object] `USERTAG`'); + expect(embedFieldUser.inline).toBeTruthy(); + + // Embed -> Channel Field + const embedFieldChannel = embed.fields[1]; + + expect(embedFieldChannel.name).toBe('Channel'); + expect(embedFieldChannel.value).toBe('[object Object]'); + expect(embedFieldChannel.inline).toBeTruthy(); + + // Embed -> Before Field + const embedFieldBefore = embed.fields[2]; + + expect(embedFieldBefore.name).toBe('Before'); + expect(embedFieldBefore.value).toBe('```Old Message```'); + + // Embed -> After Field + const embedFieldAfter = embed.fields[3]; + + expect(embedFieldAfter.name).toBe('After'); + expect(embedFieldAfter.value).toBe('```*none*```'); + }); +}); \ No newline at end of file diff --git a/tests/helpers/StringTools.test.ts b/tests/helpers/StringTools.test.ts new file mode 100644 index 0000000..ae6134b --- /dev/null +++ b/tests/helpers/StringTools.test.ts @@ -0,0 +1,11 @@ +import StringTools from "../../src/helpers/StringTools"; + +describe('Capitalise', () => { + test('Expect sentence to be captilised', () => { + const inputString = 'the big brown fox jumps over the lazy dog'; + + const result = StringTools.Capitalise(inputString); + + expect(result).toBe('The Big Brown Fox Jumps Over The Lazy Dog'); + }); +}); \ No newline at end of file diff --git a/tests/helpers/embeds/ErrorEmbed.test.ts b/tests/helpers/embeds/ErrorEmbed.test.ts new file mode 100644 index 0000000..76f0b9b --- /dev/null +++ b/tests/helpers/embeds/ErrorEmbed.test.ts @@ -0,0 +1,57 @@ +import { Message } from "discord.js"; +import { ICommandContext } from "../../../src/contracts/ICommandContext"; +import ErrorEmbed from "../../../src/helpers/embeds/ErrorEmbed"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + EMBED_COLOUR_ERROR: '0xd52803' + } + + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new ErrorEmbed(context, 'Error Message'); + + expect(errorEmbed.color?.toString()).toBe('13969411'); // 0xd52803 in decimal + expect(errorEmbed.description).toBe('Error Message'); + expect(errorEmbed.context).toBe(context); + }); +}); + +describe('SendToCurrentChannel', () => { + test('Expect embed to be sent to the current channel in context', () => { + process.env = { + EMBED_COLOUR_ERROR: '0xd52803' + } + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new ErrorEmbed(context, 'Error Message'); + + errorEmbed.SendToCurrentChannel(); + + expect(messageChannelSend).toBeCalledWith(errorEmbed); + }); +}); \ No newline at end of file diff --git a/tests/helpers/embeds/EventEmbed.test.ts b/tests/helpers/embeds/EventEmbed.test.ts new file mode 100644 index 0000000..f5932e3 --- /dev/null +++ b/tests/helpers/embeds/EventEmbed.test.ts @@ -0,0 +1,222 @@ +import { Guild, Message, TextChannel, User } from "discord.js"; +import { ICommandContext } from "../../../src/contracts/ICommandContext"; +import EventEmbed from "../../../src/helpers/embeds/EventEmbed"; + +beforeEach(() => { + process.env = {}; + jest.resetAllMocks(); +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const guild = {} as unknown as Guild; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + expect(errorEmbed.color?.toString()).toBe('13969411'); // 0xd52803 in decimal + expect(errorEmbed.title).toBe('Event Message'); + expect(errorEmbed.guild).toBe(guild); + }); +}); + +describe('AddUser', () => { + test('Given setThumbnail is false, add field WITHOUT user thumbnail', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const addField = jest.fn(); + const setThumbnail = jest.fn(); + + const guild = {} as unknown as Guild; + + const user = { + tag: 'USERTAG' + } as unknown as User; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.addField = addField; + errorEmbed.setThumbnail = setThumbnail; + + errorEmbed.AddUser('User', user); + + expect(addField).toBeCalledWith('User', '[object Object] `USERTAG`', true); + expect(setThumbnail).not.toBeCalled(); + }); + + test('Given setThumbnail is true, add field WITH user thumbnail', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const addField = jest.fn(); + const setThumbnail = jest.fn(); + const displayAvatarURL = jest.fn() + .mockReturnValue('image0.png'); + + const guild = {} as unknown as Guild; + + const user = { + tag: 'USERTAG', + displayAvatarURL: displayAvatarURL + } as unknown as User; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.addField = addField; + errorEmbed.setThumbnail = setThumbnail; + + errorEmbed.AddUser('User', user, true); + + expect(addField).toBeCalledWith('User', '[object Object] `USERTAG`', true); + expect(setThumbnail).toBeCalledWith('image0.png'); + expect(displayAvatarURL).toBeCalled(); + }); +}); + +describe('SendToChannel', () => { + test('Given channel can be found, expect embed to be sent to that channel', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const channel = { + send: channelSend + } as unknown as TextChannel; + + const guildChannelsCacheFind = jest.fn() + .mockReturnValue(channel); + + const guild = { + channels: { + cache: { + find: guildChannelsCacheFind + } + } + } as unknown as Guild; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel('channel-name'); + + expect(guildChannelsCacheFind).toBeCalledTimes(1); + expect(channelSend).toBeCalledWith(errorEmbed); + }); + + test('Given channel can NOT be found, expect error logged', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const guildChannelsCacheFind = jest.fn() + .mockReturnValue(null); + + const guild = { + channels: { + cache: { + find: guildChannelsCacheFind + } + } + } as unknown as Guild; + + console.error = jest.fn(); + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel('channel-name'); + + expect(guildChannelsCacheFind).toBeCalledTimes(1); + expect(console.error).toBeCalledWith('Unable to find channel channel-name'); + }); +}); + +describe('SendToMessageLogsChannel', () => { + describe('Expect SendToChannel caleld with CHANNELS_LOGS_MESSAGE as parameter', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const sendToChannel = jest.fn(); + + const guild = {} as unknown as Guild; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + errorEmbed.SendToMessageLogsChannel(); + + expect(sendToChannel).toBeCalledWith('message-logs'); + }); +}); + +describe('SendToMemberLogsChannel', () => { + describe('Expect SendToChannel caleld with CHANNELS_LOGS_MEMBER as parameter', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const sendToChannel = jest.fn(); + + const guild = {} as unknown as Guild; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + errorEmbed.SendToMemberLogsChannel(); + + expect(sendToChannel).toBeCalledWith('member-logs'); + }); +}); + +describe('SendToModLogsChannel', () => { + describe('Expect SendToChannel caleld with CHANNELS_LOGS_MOD as parameter', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const sendToChannel = jest.fn(); + + const guild = {} as unknown as Guild; + + const errorEmbed = new EventEmbed(guild, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + errorEmbed.SendToModLogsChannel(); + + expect(sendToChannel).toBeCalledWith('mod-logs'); + }); +}); \ No newline at end of file diff --git a/tests/helpers/embeds/LogEmbed.test.ts b/tests/helpers/embeds/LogEmbed.test.ts new file mode 100644 index 0000000..40abd19 --- /dev/null +++ b/tests/helpers/embeds/LogEmbed.test.ts @@ -0,0 +1,331 @@ +import { Guild, Message, TextChannel, User } from "discord.js"; +import { ICommandContext } from "../../../src/contracts/ICommandContext"; +import LogEmbed from "../../../src/helpers/embeds/LogEmbed"; + +beforeEach(() => { + process.env = {}; + jest.resetAllMocks(); +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Log Message'); + + expect(errorEmbed.color?.toString()).toBe('13969411'); // 0xd52803 in decimal + expect(errorEmbed.title).toBe('Log Message'); + expect(errorEmbed.context).toBe(context); + }); +}); + +describe('AddUser', () => { + test('Given setThumbnail is false, add field WITHOUT user thumbnail', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const addField = jest.fn(); + const setThumbnail = jest.fn(); + + const user = { + tag: 'USERTAG' + } as unknown as User; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + author: user + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.addField = addField; + errorEmbed.setThumbnail = setThumbnail; + + errorEmbed.AddUser('User', user); + + expect(addField).toBeCalledWith('User', '[object Object] `USERTAG`', true); + expect(setThumbnail).not.toBeCalled(); + }); + + test('Given setThumbnail is true, add field WITH user thumbnail', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const addField = jest.fn(); + const setThumbnail = jest.fn(); + const displayAvatarURL = jest.fn() + .mockReturnValue('image0.png'); + + const user = { + tag: 'USERTAG', + displayAvatarURL: displayAvatarURL + } as unknown as User; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + author: user + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.addField = addField; + errorEmbed.setThumbnail = setThumbnail; + + errorEmbed.AddUser('User', user, true); + + expect(addField).toBeCalledWith('User', '[object Object] `USERTAG`', true); + expect(setThumbnail).toBeCalledWith('image0.png'); + expect(displayAvatarURL).toBeCalled(); + }); +}); + +describe('SendToChannel', () => { + test('Given channel can be found, expect embed to be sent to that channel', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const channelSend = jest.fn(); + + const channel = { + send: channelSend + } as unknown as TextChannel; + + const guildChannelsCacheFind = jest.fn() + .mockReturnValue(channel); + + const guild = { + channels: { + cache: { + find: guildChannelsCacheFind + } + } + } as unknown as Guild; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + guild: guild + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.SendToChannel('channel-name'); + + expect(guildChannelsCacheFind).toBeCalledTimes(1); + expect(channelSend).toBeCalledWith(errorEmbed); + }); + + test('Given channel can NOT be found, expect error logged', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const guildChannelsCacheFind = jest.fn() + .mockReturnValue(null); + + const guild = { + channels: { + cache: { + find: guildChannelsCacheFind + } + } + } as unknown as Guild; + + console.error = jest.fn(); + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + guild: guild + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.SendToChannel('channel-name'); + + expect(guildChannelsCacheFind).toBeCalledTimes(1); + }); +}); + +describe('SendToMessageLogsChannel', () => { + describe('Expect SendToChannel caleld with CHANNELS_LOGS_MESSAGE as parameter', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const sendToChannel = jest.fn(); + + const guild = {} as unknown as Guild; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + errorEmbed.SendToMessageLogsChannel(); + + expect(sendToChannel).toBeCalledWith('message-logs'); + }); +}); + +describe('SendToMemberLogsChannel', () => { + describe('Expect SendToChannel caleld with CHANNELS_LOGS_MEMBER as parameter', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const sendToChannel = jest.fn(); + + const guild = {} as unknown as Guild; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + errorEmbed.SendToMemberLogsChannel(); + + expect(sendToChannel).toBeCalledWith('member-logs'); + }); +}); + +describe('SendToModLogsChannel', () => { + describe('Expect SendToChannel caleld with CHANNELS_LOGS_MOD as parameter', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const sendToChannel = jest.fn(); + + const guild = {} as unknown as Guild; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new LogEmbed(context, 'Event Message'); + + errorEmbed.SendToChannel = sendToChannel; + + errorEmbed.SendToModLogsChannel(); + + expect(sendToChannel).toBeCalledWith('mod-logs'); + }); +}); \ No newline at end of file diff --git a/tests/helpers/embeds/PublicEmbed.test.ts b/tests/helpers/embeds/PublicEmbed.test.ts new file mode 100644 index 0000000..6b45797 --- /dev/null +++ b/tests/helpers/embeds/PublicEmbed.test.ts @@ -0,0 +1,67 @@ +import { Guild, Message, TextChannel, User } from "discord.js"; +import { ICommandContext } from "../../../src/contracts/ICommandContext"; +import PublicEmbed from "../../../src/helpers/embeds/PublicEmbed"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties to be set', () => { + process.env = { + EMBED_COLOUR: '0xd52803', + CHANNELS_LOGS_MESSAGE: 'message-logs', + CHANNELS_LOGS_MEMBER: 'member-logs', + CHANNELS_LOGS_MOD: 'mod-logs' + } + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new PublicEmbed(context, 'Log Message', 'Log Description'); + + expect(errorEmbed.color?.toString()).toBe('13969411'); // 0xd52803 in decimal + expect(errorEmbed.title).toBe('Log Message'); + expect(errorEmbed.description).toBe('Log Description'); + expect(errorEmbed.context).toBe(context); + }); +}); + +describe('SendToCurrentChannel', () => { + test('Expect embed to be sent to the current channel in context', () => { + process.env = { + EMBED_COLOUR_ERROR: '0xd52803' + } + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'command', + args: [], + message: message + }; + + const errorEmbed = new PublicEmbed(context, 'Message', 'Description'); + + errorEmbed.SendToCurrentChannel(); + + expect(messageChannelSend).toBeCalledWith(errorEmbed); + }); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e95e211..b9359b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,6 @@ # yarn lockfile v1 -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" @@ -150,7 +143,7 @@ "@babel/traverse" "^7.16.5" "@babel/types" "^7.16.0" -"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": +"@babel/highlight@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== @@ -293,18 +286,6 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - "@discordjs/collection@^0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c" @@ -319,35 +300,6 @@ combined-stream "^1.0.8" mime-types "^2.1.12" -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== - dependencies: - ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" - -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== - dependencies: - "@humanwhocodes/object-schema" "^1.2.0" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -564,26 +516,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.1.17" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64" @@ -732,27 +664,17 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-jsx@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.4.1: +acorn@^8.2.4: version "8.6.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== @@ -764,31 +686,6 @@ agent-base@6: dependencies: debug "4" -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - 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" - -ajv@^8.0.1: - version "8.8.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" - integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -828,11 +725,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -840,11 +732,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1105,12 +992,7 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1152,7 +1034,7 @@ debug@4, debug@^4.1.0: dependencies: ms "2.1.2" -debug@^4.0.1, debug@^4.1.1: +debug@^4.1.1: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -1176,7 +1058,7 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= -deep-is@^0.1.3, deep-is@~0.1.3: +deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -1206,11 +1088,6 @@ diff-sequences@^27.4.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - discord.js@12.5.3: version "12.5.3" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.5.3.tgz#56820d473c24320871df9ea0bbc6b462f21cf85c" @@ -1225,13 +1102,6 @@ discord.js@12.5.3: tweetnacl "^1.0.3" ws "^7.4.4" -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - domexception@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -1271,13 +1141,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1293,11 +1156,6 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -1310,111 +1168,12 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint@^7.17.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== - dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: +estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -1461,17 +1220,12 @@ expect@^27.4.2: jest-message-util "^27.4.2" jest-regex-util "^27.4.0" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: +fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -1483,13 +1237,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1505,19 +1252,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" - integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== - form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -1542,11 +1276,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1574,13 +1303,6 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob-parent@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -1605,13 +1327,6 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" - integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== - dependencies: - type-fest "^0.20.2" - got@^11.8.3: version "11.8.3" resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" @@ -1705,19 +1420,6 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - import-local@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" @@ -1766,7 +1468,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2324,21 +2026,6 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - json5@2.x, json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" @@ -2363,14 +2050,6 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -2391,16 +2070,6 @@ lodash.memoize@4.x: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= - lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -2425,7 +2094,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@1.x, make-error@^1.1.1: +make-error@1.x: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -2564,18 +2233,6 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" @@ -2600,13 +2257,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse5@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" @@ -2654,11 +2304,6 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -2679,11 +2324,6 @@ prism-media@^1.2.9: resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.2.tgz#a1f04423ec15d22f3d62b1987b6a25dc49aad13b" integrity sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -2705,7 +2345,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -2728,21 +2368,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -regexpp@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - resolve-alpn@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -2755,11 +2385,6 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -2785,7 +2410,7 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -2809,7 +2434,7 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -semver@7.x, semver@^7.2.1, semver@^7.3.2: +semver@7.x, semver@^7.3.2: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -2853,15 +2478,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - source-map-support@^0.5.6: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -2905,7 +2521,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2931,11 +2547,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2970,17 +2581,6 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.9: - version "6.7.3" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" - integrity sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw== - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -2998,11 +2598,6 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - throat@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -3065,36 +2660,11 @@ ts-jest@^27.1.2: semver "7.x" yargs-parser "20.x" -ts-node@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" - integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - yn "3.1.1" - tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -3107,11 +2677,6 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -3134,18 +2699,6 @@ universalify@^0.1.2: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - v8-to-istanbul@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" @@ -3227,7 +2780,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3, word-wrap@~1.2.3: +word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -3298,8 +2851,3 @@ yargs@^16.2.0: string-width "^4.2.0" y18n "^5.0.5" yargs-parser "^20.2.2" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From 97633451edb005aefce722ff16cf998c0bd06641 Mon Sep 17 00:00:00 2001 From: Vylpes <ethan@vylpes.com> Date: Sat, 5 Feb 2022 09:55:40 +0000 Subject: [PATCH 26/28] Update rules with blog website and event spoilers rule" (#106) Signed-off-by: Ethan Lane <ethan@vylpes.com> --- data/rules/rules.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/rules/rules.json b/data/rules/rules.json index e65a2ce..9fa7a1e 100644 --- a/data/rules/rules.json +++ b/data/rules/rules.json @@ -38,7 +38,10 @@ "Please don't bring up drama from other servers, keep that to DMs", "", "**Bot Abuse**", - "Don't abuse the bots or you will be blocked from using them" + "Don't abuse the bots or you will be blocked from using them", + "", + "**Event Spoilers**", + "Contents of events and keynotes, such as the Nintendo Direct, must be spoken about in events, this rule applies for up to 24 hours after the event ends. Even though we will only enforce talking there for a set time, please be considerate of those who haven't watched the event yet." ] }, { @@ -77,8 +80,9 @@ "YouTube: https://www.youtube.com/channel/UCwPlzKwCmP5Q9bCX3fHk2BA", "Patreon: https://www.patreon.com/vylpes", "Twitch: https://www.twitch.tv/vylpes_", - "Twitter: https://twitter.com/vylpes" + "Twitter: https://twitter.com/vylpes", + "Blog: https://vylpes.xyz" ], - "footer": "Last updated 22/04/2021" + "footer": "Last updated 01/02/2022" } ] \ No newline at end of file From de236dfd30092f3413bb6216cd32065976b1e6e6 Mon Sep 17 00:00:00 2001 From: Vylpes <ethan@vylpes.com> Date: Sat, 5 Feb 2022 21:09:27 +0000 Subject: [PATCH 27/28] Containerise bot (#107) --- .dockerignore | 11 +++++++++++ Dockerfile | 15 +++++++++++++++ docker-compose.yml | 4 ++++ 3 files changed, 30 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0e28e03 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +node_modules/ +tests/ +coverage/ +.github/ +.gitlab/ + +.env.template +.gitlab-ci.yml +jest.config.js +jest.setup.js +README.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..51e8b21 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Create app and work directory +FROM node:16 +WORKDIR /vylbot + +# Install dependencies +COPY package.json . +COPY yarn.lock . +RUN yarn install + +# Bundle app source +COPY . . +RUN yarn build + +# Run the app source +CMD [ "yarn", "start" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..da68dfd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,4 @@ +version: "3.9" +services: + discord: + build: . \ No newline at end of file From c8edd1b4c5effd89878c3eeb0608e9fb77079155 Mon Sep 17 00:00:00 2001 From: Vylpes <ethan@vylpes.com> Date: Sat, 5 Feb 2022 21:09:35 +0000 Subject: [PATCH 28/28] Add moderator names to audit reason (#108) --- src/commands/ban.ts | 2 +- src/commands/kick.ts | 2 +- src/commands/mute.ts | 2 +- src/commands/role.ts | 4 +- src/commands/unmute.ts | 2 +- tests/commands/ban.test.ts | 160 +++++++++++++++++++++++++++++++++- tests/commands/kick.test.ts | 103 +++++++++++++++++++++- tests/commands/mute.test.ts | 142 +++++++++++++++++++++++++++++- tests/commands/role.test.ts | 4 +- tests/commands/unmute.test.ts | 142 +++++++++++++++++++++++++++++- 10 files changed, 551 insertions(+), 12 deletions(-) diff --git a/src/commands/ban.ts b/src/commands/ban.ts index fddac6c..f565834 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -67,7 +67,7 @@ export default class Ban extends Command { const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been banned`); - await targetMember.ban({ reason: reason }); + await targetMember.ban({ reason: `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}` }); logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); diff --git a/src/commands/kick.ts b/src/commands/kick.ts index a98c729..852b57a 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -70,7 +70,7 @@ export default class Kick extends Command { const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been kicked`); - await targetMember.kick(reason); + await targetMember.kick(`Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`); logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); diff --git a/src/commands/mute.ts b/src/commands/mute.ts index dd0f81a..4b63527 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -83,7 +83,7 @@ export default class Mute extends Command { }; } - await targetMember.roles.add(mutedRole, reason); + await targetMember.roles.add(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`); logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); diff --git a/src/commands/role.ts b/src/commands/role.ts index e9e7475..88bd5b8 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -74,7 +74,7 @@ export default class Role extends Command { } public async AddRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> { - await context.message.member?.roles.add(role); + await context.message.member?.roles.add(role, "Toggled with role command"); const embed = new PublicEmbed(context, "", `Gave role: ${role.name}`); embed.SendToCurrentChannel(); @@ -86,7 +86,7 @@ export default class Role extends Command { } public async RemoveRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> { - await context.message.member?.roles.remove(role); + await context.message.member?.roles.remove(role, "Toggled with role command"); const embed = new PublicEmbed(context, "", `Removed role: ${role.name}`); embed.SendToCurrentChannel(); diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts index 51face6..6b5901c 100644 --- a/src/commands/unmute.ts +++ b/src/commands/unmute.ts @@ -83,7 +83,7 @@ export default class Unmute extends Command { }; } - await targetMember.roles.remove(mutedRole, reason); + await targetMember.roles.remove(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`); logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); diff --git a/tests/commands/ban.test.ts b/tests/commands/ban.test.ts index 6ed09e6..8391abb 100644 --- a/tests/commands/ban.test.ts +++ b/tests/commands/ban.test.ts @@ -89,7 +89,80 @@ describe('Execute', () => { expect(messageChannelSend).toBeCalledTimes(1); expect(logChannel.send).toBeCalledTimes(1); - expect(mentionedMember.ban).toBeCalledWith({ reason: 'Test Reason' }); + expect(mentionedMember.ban).toBeCalledWith({ reason: 'Moderator: AUTHORTAG, Reason: Test Reason' }); + }); + + test('Given moderator did not supply a reason, expect default message', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).toBeCalledTimes(1); + expect(mentionedMember.ban).toBeCalledWith({ reason: 'Moderator: AUTHORTAG, Reason: *none*' }); }); test('Given user has permissions, expect embeds to be correct', async () => { @@ -257,6 +330,91 @@ describe('Execute', () => { expect(fieldReason.value).toBe('Test Reason'); }); + test('Given moderator did not supply a reason, expect reason field to be default message', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn().mockReturnValue('URL'), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + const logEmbed = result.embeds[0]; + + const fieldUser = logEmbed.fields[0]; + const fieldModerator = logEmbed.fields[1]; + const fieldReason = logEmbed.fields[2]; + + expect(fieldUser.name).toBe("User"); + expect(fieldUser.value).toBe("[object Object] `USERTAG`"); + expect(logEmbed.thumbnail?.url).toBe("URL"); + + expect(fieldModerator.name).toBe('Moderator'); + expect(fieldModerator.value).toBe('[object Object] `AUTHORTAG`'); + + expect(fieldReason.name).toBe('Reason'); + expect(fieldReason.value).toBe('*none*'); + }); + test('Given user is not mentioned, expect error embed to be sent', async () => { process.env = { ROLES_MODERATOR: 'Moderator', diff --git a/tests/commands/kick.test.ts b/tests/commands/kick.test.ts index c5ccd68..f0fc6a4 100644 --- a/tests/commands/kick.test.ts +++ b/tests/commands/kick.test.ts @@ -94,7 +94,7 @@ describe('Execute', () => { expect(messageChannelSend).toBeCalledTimes(1); expect(logChannel.send).toBeCalledTimes(1); - expect(member.kick).toBeCalledWith('Test Reason'); + expect(member.kick).toBeCalledWith('Moderator: AUTHORTAG, Reason: Test Reason'); expect(result.embeds.length).toBe(2); @@ -124,6 +124,107 @@ describe('Execute', () => { expect(logEmbedFieldReason.value).toBe('Test Reason'); }); + test('Given moderator did not supply a reason, expect default reason to be added', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const member = { + kickable: true, + kick: jest.fn() + } as unknown as GuildMember; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: "kick", + args: ["USER"], + message: message + } + + const kick = new Kick(); + + const result = await kick.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).toBeCalledTimes(1); + expect(member.kick).toBeCalledWith('Moderator: AUTHORTAG, Reason: *none*'); + + expect(result.embeds.length).toBe(2); + + // Log Embed + const logEmbed = result.embeds[0]; + + expect(logEmbed.title).toBe('Member Kicked'); + expect(logEmbed.fields.length).toBe(3); + + // Log Embed -> User Field + const logEmbedFieldUser = logEmbed.fields[0]; + + expect(logEmbedFieldUser.name).toBe('User'); + expect(logEmbedFieldUser.value).toBe('[object Object] `USERTAG`'); + expect(logEmbedFieldUser.inline).toBeTruthy(); + + // Log Embed -> Moderator Field + const logEmbedFieldModerator = logEmbed.fields[1]; + + expect(logEmbedFieldModerator.name).toBe('Moderator'); + expect(logEmbedFieldModerator.value).toBe('[object Object] `AUTHORTAG`'); + + // Log Embed -> Reason Field + const logEmbedFieldReason = logEmbed.fields[2]; + + expect(logEmbedFieldReason.name).toBe('Reason'); + expect(logEmbedFieldReason.value).toBe('*none*'); + }); + test('Given target user is not found, expect user does not exist error', async () => { process.env = { CHANNELS_LOGS_MOD: 'mod-logs' diff --git a/tests/commands/mute.test.ts b/tests/commands/mute.test.ts index a0137f5..974f676 100644 --- a/tests/commands/mute.test.ts +++ b/tests/commands/mute.test.ts @@ -121,7 +121,7 @@ describe('Execute', () => { expect(messageGuildRolesCacheFind).toBeCalledTimes(1); expect(messageGuildChannelsCacheFind).toBeCalledTimes(1); expect(messageChannelSend).toBeCalledTimes(1); - expect(member.roles.add).toBeCalledWith(role, 'Test Reason'); + expect(member.roles.add).toBeCalledWith(role, 'Moderator: AUTHORTAG, Reason: Test Reason'); expect(result.embeds.length).toBe(2); @@ -144,6 +144,146 @@ describe('Execute', () => { expect(logEmbedModeratorField.name).toBe('Moderator'); expect(logEmbedModeratorField.value).toBe('[object Object] `AUTHORTAG`'); + // Log Embed -> Reason Field + const logEmbedFieldReason = logEmbed.fields[2]; + + expect(logEmbedFieldReason.name).toBe('Reason'); + expect(logEmbedFieldReason.value).toBe('Test Reason'); + + // Public Embed + const publicEmbed = result.embeds[1]; + + expect(publicEmbed.title).toBe(''); + expect(publicEmbed.description).toBe('[object Object] has been muted'); + }); + + test('Given moderator did not supply a reason, expect default reason used', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + add: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER'], + message: message + }; + + const mute = new Mute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).toBeCalledTimes(1); + expect(member.roles.add).toBeCalledWith(role, 'Moderator: AUTHORTAG, Reason: *none*'); + + expect(result.embeds.length).toBe(2); + + // Log Embed + const logEmbed = result.embeds[0]; + + expect(logEmbed.title).toBe('Member Muted'); + expect(logEmbed.fields.length).toBe(3); + + // Log Embed -> User Field + const logEmbedUserField = logEmbed.fields[0]; + + expect(logEmbedUserField.name).toBe('User'); + expect(logEmbedUserField.value).toBe('[object Object] `USERTAG`'); + expect(logEmbedUserField.inline).toBeTruthy(); + + // Log Embed -> Moderator Field + const logEmbedModeratorField = logEmbed.fields[1]; + + expect(logEmbedModeratorField.name).toBe('Moderator'); + expect(logEmbedModeratorField.value).toBe('[object Object] `AUTHORTAG`'); + + // Log Embed -> Reason Field + const logEmbedFieldReason = logEmbed.fields[2]; + + expect(logEmbedFieldReason.name).toBe('Reason'); + expect(logEmbedFieldReason.value).toBe('*none*'); + // Public Embed const publicEmbed = result.embeds[1]; diff --git a/tests/commands/role.test.ts b/tests/commands/role.test.ts index f72d5da..d14d56a 100644 --- a/tests/commands/role.test.ts +++ b/tests/commands/role.test.ts @@ -355,7 +355,7 @@ describe('AddRole', () => { const result = await role.AddRole(context, discordRole); - expect(guildMemberRoleManager.add).toBeCalledWith(discordRole); + expect(guildMemberRoleManager.add).toBeCalledWith(discordRole, "Toggled with role command"); expect(messageChannelSend).toBeCalled(); expect(result.embeds.length).toBe(1); @@ -397,7 +397,7 @@ describe('RemoveRole', () => { const result = await role.RemoveRole(context, discordRole); - expect(guildMemberRoleManager.remove).toBeCalledWith(discordRole); + expect(guildMemberRoleManager.remove).toBeCalledWith(discordRole, "Toggled with role command"); expect(messageChannelSend).toBeCalled(); expect(result.embeds.length).toBe(1); diff --git a/tests/commands/unmute.test.ts b/tests/commands/unmute.test.ts index 1449b39..b61a8f8 100644 --- a/tests/commands/unmute.test.ts +++ b/tests/commands/unmute.test.ts @@ -119,7 +119,7 @@ describe('Execute', () => { expect(messageGuildRolesCacheFind).toBeCalledTimes(1); expect(messageGuildChannelsCacheFind).toBeCalledTimes(1); expect(messageChannelSend).toBeCalledTimes(1); - expect(member.roles.remove).toBeCalledWith(role, 'Test Reason'); + expect(member.roles.remove).toBeCalledWith(role, 'Moderator: AUTHORTAG, Reason: Test Reason'); expect(result.embeds.length).toBe(2); @@ -142,6 +142,146 @@ describe('Execute', () => { expect(logEmbedModeratorField.name).toBe('Moderator'); expect(logEmbedModeratorField.value).toBe('[object Object] `AUTHORTAG`'); + // Log Embed -> Reason Field + const logEmbedFieldReason = logEmbed.fields[2]; + + expect(logEmbedFieldReason.name).toBe('Reason'); + expect(logEmbedFieldReason.value).toBe('Test Reason'); + + // Public Embed + const publicEmbed = result.embeds[1]; + + expect(publicEmbed.title).toBe(''); + expect(publicEmbed.description).toBe('[object Object] has been unmuted'); + }); + + test('Given moderator did not supply a reason, expect default reason is used', async () => { + process.env = { + CHANNELS_LOGS_MOD: 'mod-logs', + ROLES_MUTED: 'Muted' + }; + + const user = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + + const messageAuthor = { + tag: 'AUTHORTAG' + } as unknown as User; + + const member = { + manageable: true, + roles: { + remove: jest.fn() + } + } as unknown as GuildMember; + + const role = { + name: 'Muted' + } as unknown as Role; + + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(user); + const messageGuildMember = jest.fn() + .mockReturnValue(member); + const messageGuildRolesCacheFind = jest.fn() + .mockImplementation((callback): Role | undefined => { + const result = callback(role); + + if (!result) { + return undefined; + } + + return role; + }); + const messageChannelSend = jest.fn(); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember, + available: true, + roles: { + cache: { + find: messageGuildRolesCacheFind + } + }, + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + } + }, + channel: { + send: messageChannelSend + }, + author: messageAuthor + } as unknown as Message; + + const context: ICommandContext = { + name: 'mute', + args: ['USER'], + message: message + }; + + const mute = new Unmute(); + + const result = await mute.execute(context); + + expect(messageMentionsUsersFirst).toBeCalledTimes(1); + expect(messageGuildMember).toBeCalledWith(user); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildChannelsCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).toBeCalledTimes(1); + expect(member.roles.remove).toBeCalledWith(role, 'Moderator: AUTHORTAG, Reason: *none*'); + + expect(result.embeds.length).toBe(2); + + // Log Embed + const logEmbed = result.embeds[0]; + + expect(logEmbed.title).toBe('Member Unmuted'); + expect(logEmbed.fields.length).toBe(3); + + // Log Embed -> User Field + const logEmbedUserField = logEmbed.fields[0]; + + expect(logEmbedUserField.name).toBe('User'); + expect(logEmbedUserField.value).toBe('[object Object] `USERTAG`'); + expect(logEmbedUserField.inline).toBeTruthy(); + + // Log Embed -> Moderator Field + const logEmbedModeratorField = logEmbed.fields[1]; + + expect(logEmbedModeratorField.name).toBe('Moderator'); + expect(logEmbedModeratorField.value).toBe('[object Object] `AUTHORTAG`'); + + // Log Embed -> Reason Field + const logEmbedFieldReason = logEmbed.fields[2]; + + expect(logEmbedFieldReason.name).toBe('Reason'); + expect(logEmbedFieldReason.value).toBe('*none*'); + // Public Embed const publicEmbed = result.embeds[1];